Java tutorial
/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.wear.widget; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Paint; import android.os.Build; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.support.v4.content.ContextCompat; import android.support.v4.widget.CircularProgressDrawable; import android.support.wear.R; import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.widget.FrameLayout; /** * {@link CircularProgressLayout} adds a circular countdown timer behind the view it contains, * typically used to automatically confirm an operation after a short delay has elapsed. * * <p>The developer can specify a countdown interval via {@link #setTotalTime(long)} and a listener * via {@link #setOnTimerFinishedListener(OnTimerFinishedListener)} to be called when the time has * elapsed after {@link #startTimer()} has been called. Tap action can be received via {@link * #setOnClickListener(OnClickListener)} and can be used to cancel the timer via {@link * #stopTimer()} method. * * <p>Alternatively, this layout can be used to show indeterminate progress by calling {@link * #setIndeterminate(boolean)} method. */ @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public class CircularProgressLayout extends FrameLayout { /** * Update interval for 60 fps. */ private static final long DEFAULT_UPDATE_INTERVAL = 1000 / 60; /** * Starting rotation for the progress indicator. Geometric clockwise [0..360] degree range * correspond to [0..1] range. 0.75 corresponds to 12 o'clock direction on a watch. */ private static final float DEFAULT_ROTATION = 0.75f; /** * Used as background of this layout. */ private CircularProgressDrawable mProgressDrawable; /** * Used to control this layout. */ private CircularProgressLayoutController mController; /** * Angle for the progress to start from. */ private float mStartingRotation = DEFAULT_ROTATION; /** * Duration of the timer in milliseconds. */ private long mTotalTime; /** * Interface to implement for listening to {@link * OnTimerFinishedListener#onTimerFinished(CircularProgressLayout)} event. */ public interface OnTimerFinishedListener { /** * Called when the timer started by {@link #startTimer()} method finishes. * * @param layout {@link CircularProgressLayout} that calls this method. */ void onTimerFinished(CircularProgressLayout layout); } public CircularProgressLayout(Context context) { this(context, null); } public CircularProgressLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CircularProgressLayout(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public CircularProgressLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mProgressDrawable = new CircularProgressDrawable(context); mProgressDrawable.setProgressRotation(DEFAULT_ROTATION); mProgressDrawable.setStrokeCap(Paint.Cap.BUTT); setBackground(mProgressDrawable); // If a child view is added, make it center aligned so it fits in the progress drawable. setOnHierarchyChangeListener(new OnHierarchyChangeListener() { @Override public void onChildViewAdded(View parent, View child) { // Ensure that child view is aligned in center LayoutParams params = (LayoutParams) child.getLayoutParams(); params.gravity = Gravity.CENTER; child.setLayoutParams(params); } @Override public void onChildViewRemoved(View parent, View child) { } }); mController = new CircularProgressLayoutController(this); Resources r = context.getResources(); TypedArray a = r.obtainAttributes(attrs, R.styleable.CircularProgressLayout); if (a.getType(R.styleable.CircularProgressLayout_colorSchemeColors) == TypedValue.TYPE_REFERENCE || !a.hasValue(R.styleable.CircularProgressLayout_colorSchemeColors)) { int arrayResId = a.getResourceId(R.styleable.CircularProgressLayout_colorSchemeColors, R.array.circular_progress_layout_color_scheme_colors); setColorSchemeColors(getColorListFromResources(r, arrayResId)); } else { setColorSchemeColors(a.getColor(R.styleable.CircularProgressLayout_colorSchemeColors, Color.BLACK)); } setStrokeWidth(a.getDimensionPixelSize(R.styleable.CircularProgressLayout_strokeWidth, r.getDimensionPixelSize(R.dimen.circular_progress_layout_stroke_width))); setBackgroundColor(a.getColor(R.styleable.CircularProgressLayout_backgroundColor, ContextCompat.getColor(context, R.color.circular_progress_layout_background_color))); setIndeterminate(a.getBoolean(R.styleable.CircularProgressLayout_indeterminate, false)); a.recycle(); } private int[] getColorListFromResources(Resources resources, int arrayResId) { TypedArray colorArray = resources.obtainTypedArray(arrayResId); int[] colors = new int[colorArray.length()]; for (int i = 0; i < colorArray.length(); i++) { colors[i] = colorArray.getColor(i, 0); } colorArray.recycle(); return colors; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (getChildCount() != 0) { View childView = getChildAt(0); // Wrap the drawable around the child view mProgressDrawable.setCenterRadius(Math.min(childView.getWidth(), childView.getHeight()) / 2f); } else { // Fill the bounds if no child view is present mProgressDrawable.setCenterRadius(0f); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mController.reset(); } /** * Sets the background color of the {@link CircularProgressDrawable}, which is drawn as a circle * inside the progress drawable. Colors are in ARGB format defined in {@link Color}. * * @param color an ARGB color */ @Override public void setBackgroundColor(@ColorInt int color) { mProgressDrawable.setBackgroundColor(color); } /** * Returns the background color of the {@link CircularProgressDrawable}. * * @return an ARGB color */ @ColorInt public int getBackgroundColor() { return mProgressDrawable.getBackgroundColor(); } /** * Returns the {@link CircularProgressDrawable} used as background of this layout. * * @return {@link CircularProgressDrawable} */ @NonNull public CircularProgressDrawable getProgressDrawable() { return mProgressDrawable; } /** * Sets if progress should be shown as an indeterminate spinner. * * @param indeterminate {@code true} if indeterminate spinner should be shown, {@code false} * otherwise. */ public void setIndeterminate(boolean indeterminate) { mController.setIndeterminate(indeterminate); } /** * Returns if progress is showing as an indeterminate spinner. * * @return {@code true} if indeterminate spinner is shown, {@code false} otherwise. */ public boolean isIndeterminate() { return mController.isIndeterminate(); } /** * Sets the total time in milliseconds for the timer to countdown to. Calling this method while * the timer is already running will not change the duration of the current timer. * * @param totalTime total time in milliseconds */ public void setTotalTime(long totalTime) { if (totalTime <= 0) { throw new IllegalArgumentException("Total time should be greater than zero."); } mTotalTime = totalTime; } /** * Returns the total time in milliseconds for the timer to countdown to. * * @return total time in milliseconds */ public long getTotalTime() { return mTotalTime; } /** * Starts the timer countdown. Once the countdown is finished, if there is an {@link * OnTimerFinishedListener} registered by {@link * #setOnTimerFinishedListener(OnTimerFinishedListener)} method, its * {@link OnTimerFinishedListener#onTimerFinished(CircularProgressLayout)} method is called. If * this method is called while there is already a running timer, it will restart the timer. */ public void startTimer() { mController.startTimer(mTotalTime, DEFAULT_UPDATE_INTERVAL); mProgressDrawable.setProgressRotation(mStartingRotation); } /** * Stops the timer countdown. If there is no timer running, calling this method will not do * anything. */ public void stopTimer() { mController.stopTimer(); } /** * Returns if the timer is running. * * @return {@code true} if the timer is running, {@code false} otherwise */ public boolean isTimerRunning() { return mController.isTimerRunning(); } /** * Sets the starting rotation for the progress drawable to start from. Default starting rotation * is {@code 0.75} and it corresponds clockwise geometric 270 degrees (12 o'clock on a watch) * * @param rotation starting rotation from [0..1] */ public void setStartingRotation(float rotation) { mStartingRotation = rotation; } /** * Returns the starting rotation of the progress drawable. * * @return starting rotation from [0..1] */ public float getStartingRotation() { return mStartingRotation; } /** * Sets the stroke width of the progress drawable in pixels. * * @param strokeWidth stroke width in pixels */ public void setStrokeWidth(float strokeWidth) { mProgressDrawable.setStrokeWidth(strokeWidth); } /** * Returns the stroke width of the progress drawable in pixels. * * @return stroke width in pixels */ public float getStrokeWidth() { return mProgressDrawable.getStrokeWidth(); } /** * Sets the color scheme colors of the progress drawable, which is equivalent to calling {@link * CircularProgressDrawable#setColorSchemeColors(int...)} method on background drawable of this * layout. * * @param colors list of ARGB colors */ public void setColorSchemeColors(int... colors) { mProgressDrawable.setColorSchemeColors(colors); } /** * Returns the color scheme colors of the progress drawable * * @return list of ARGB colors */ public int[] getColorSchemeColors() { return mProgressDrawable.getColorSchemeColors(); } /** * Returns the {@link OnTimerFinishedListener} that is registered to this layout. * * @return registered {@link OnTimerFinishedListener} */ @Nullable public OnTimerFinishedListener getOnTimerFinishedListener() { return mController.getOnTimerFinishedListener(); } /** * Sets the {@link OnTimerFinishedListener} to be notified when timer countdown is finished. * * @param listener {@link OnTimerFinishedListener} to be notified, or {@code null} to clear */ public void setOnTimerFinishedListener(@Nullable OnTimerFinishedListener listener) { mController.setOnTimerFinishedListener(listener); } }