/* * Copyright (C) 2006 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.widget; import android.animation.ObjectAnimator; import android.annotation.InterpolatorRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.drawable.Animatable; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ClipDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.StateListDrawable; import android.graphics.drawable.shapes.RoundRectShape; import android.graphics.drawable.shapes.Shape; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.MathUtils; import android.util.Pools.SynchronizedPool; import android.view.Gravity; import android.view.RemotableViewMethod; import android.view.View; import android.view.ViewDebug; import android.view.ViewHierarchyEncoder; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.Transformation; import android.widget.RemoteViews.RemoteView; import com.android.internal.R; import java.util.ArrayList; /** *

* Visual indicator of progress in some operation. Displays a bar to the user * representing how far the operation has progressed; the application can * change the amount of progress (modifying the length of the bar) as it moves * forward. There is also a secondary progress displayable on a progress bar * which is useful for displaying intermediate progress, such as the buffer * level during a streaming playback progress bar. *

* *

* A progress bar can also be made indeterminate. In indeterminate mode, the * progress bar shows a cyclic animation without an indication of progress. This mode is used by * applications when the length of the task is unknown. The indeterminate progress bar can be either * a spinning wheel or a horizontal bar. *

* *

The following code example shows how a progress bar can be used from * a worker thread to update the user interface to notify the user of progress: *

* *
 * public class MyActivity extends Activity {
 *     private static final int PROGRESS = 0x1;
 *
 *     private ProgressBar mProgress;
 *     private int mProgressStatus = 0;
 *
 *     private Handler mHandler = new Handler();
 *
 *     protected void onCreate(Bundle icicle) {
 *         super.onCreate(icicle);
 *
 *         setContentView(R.layout.progressbar_activity);
 *
 *         mProgress = (ProgressBar) findViewById(R.id.progress_bar);
 *
 *         // Start lengthy operation in a background thread
 *         new Thread(new Runnable() {
 *             public void run() {
 *                 while (mProgressStatus < 100) {
 *                     mProgressStatus = doWork();
 *
 *                     // Update the progress bar
 *                     mHandler.post(new Runnable() {
 *                         public void run() {
 *                             mProgress.setProgress(mProgressStatus);
 *                         }
 *                     });
 *                 }
 *             }
 *         }).start();
 *     }
 * }
* *

To add a progress bar to a layout file, you can use the {@code } element. * By default, the progress bar is a spinning wheel (an indeterminate indicator). To change to a * horizontal progress bar, apply the {@link android.R.style#Widget_ProgressBar_Horizontal * Widget.ProgressBar.Horizontal} style, like so:

* *
 * <ProgressBar
 *     style="@android:style/Widget.ProgressBar.Horizontal"
 *     ... />
* *

If you will use the progress bar to show real progress, you must use the horizontal bar. You * can then increment the progress with {@link #incrementProgressBy incrementProgressBy()} or * {@link #setProgress setProgress()}. By default, the progress bar is full when it reaches 100. If * necessary, you can adjust the maximum value (the value for a full bar) using the {@link * android.R.styleable#ProgressBar_max android:max} attribute. Other attributes available are listed * below.

* *

Another common style to apply to the progress bar is {@link * android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}, which shows a smaller * version of the spinning wheel—useful when waiting for content to load. * For example, you can insert this kind of progress bar into your default layout for * a view that will be populated by some content fetched from the Internet—the spinning wheel * appears immediately and when your application receives the content, it replaces the progress bar * with the loaded content. For example:

* *
 * <LinearLayout
 *     android:orientation="horizontal"
 *     ... >
 *     <ProgressBar
 *         android:layout_width="wrap_content"
 *         android:layout_height="wrap_content"
 *         style="@android:style/Widget.ProgressBar.Small"
 *         android:layout_marginRight="5dp" />
 *     <TextView
 *         android:layout_width="wrap_content"
 *         android:layout_height="wrap_content"
 *         android:text="@string/loading" />
 * </LinearLayout>
* *

Other progress bar styles provided by the system include:

* *

The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary * if your application uses a light colored theme (a white background).

* *

XML attributes *

* See {@link android.R.styleable#ProgressBar ProgressBar Attributes}, * {@link android.R.styleable#View View Attributes} *

* * @attr ref android.R.styleable#ProgressBar_animationResolution * @attr ref android.R.styleable#ProgressBar_indeterminate * @attr ref android.R.styleable#ProgressBar_indeterminateBehavior * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable * @attr ref android.R.styleable#ProgressBar_indeterminateDuration * @attr ref android.R.styleable#ProgressBar_indeterminateOnly * @attr ref android.R.styleable#ProgressBar_interpolator * @attr ref android.R.styleable#ProgressBar_max * @attr ref android.R.styleable#ProgressBar_maxHeight * @attr ref android.R.styleable#ProgressBar_maxWidth * @attr ref android.R.styleable#ProgressBar_minHeight * @attr ref android.R.styleable#ProgressBar_minWidth * @attr ref android.R.styleable#ProgressBar_mirrorForRtl * @attr ref android.R.styleable#ProgressBar_progress * @attr ref android.R.styleable#ProgressBar_progressDrawable * @attr ref android.R.styleable#ProgressBar_secondaryProgress */ @RemoteView public class ProgressBar extends View { private static final int MAX_LEVEL = 10000; private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200; /** Interpolator used for smooth progress animations. */ private static final DecelerateInterpolator PROGRESS_ANIM_INTERPOLATOR = new DecelerateInterpolator(); /** Duration of smooth progress animations. */ private static final int PROGRESS_ANIM_DURATION = 80; int mMinWidth; int mMaxWidth; int mMinHeight; int mMaxHeight; private int mProgress; private int mSecondaryProgress; private int mMax; private int mBehavior; private int mDuration; private boolean mIndeterminate; private boolean mOnlyIndeterminate; private Transformation mTransformation; private AlphaAnimation mAnimation; private boolean mHasAnimation; private Drawable mIndeterminateDrawable; private Drawable mProgressDrawable; private Drawable mCurrentDrawable; private ProgressTintInfo mProgressTintInfo; Bitmap mSampleTile; private boolean mNoInvalidate; private Interpolator mInterpolator; private RefreshProgressRunnable mRefreshProgressRunnable; private long mUiThreadId; private boolean mShouldStartAnimationDrawable; private boolean mInDrawing; private boolean mAttached; private boolean mRefreshIsPosted; /** Value used to track progress animation, in the range [0...1]. */ private float mVisualProgress; boolean mMirrorForRtl = false; private boolean mAggregatedIsVisible; private final ArrayList mRefreshData = new ArrayList(); private AccessibilityEventSender mAccessibilityEventSender; /** * Create a new progress bar with range 0...100 and initial progress of 0. * @param context the application environment */ public ProgressBar(Context context) { this(context, null); } public ProgressBar(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.progressBarStyle); } public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mUiThreadId = Thread.currentThread().getId(); initProgressBar(); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes); mNoInvalidate = true; final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable); if (progressDrawable != null) { // Calling setProgressDrawable can set mMaxHeight, so make sure the // corresponding XML attribute for mMaxHeight is read after calling // this method. if (needsTileify(progressDrawable)) { setProgressDrawableTiled(progressDrawable); } else { setProgressDrawable(progressDrawable); } } mDuration = a.getInt(R.styleable.ProgressBar_indeterminateDuration, mDuration); mMinWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_minWidth, mMinWidth); mMaxWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_maxWidth, mMaxWidth); mMinHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_minHeight, mMinHeight); mMaxHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_maxHeight, mMaxHeight); mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior); final int resID = a.getResourceId( com.android.internal.R.styleable.ProgressBar_interpolator, android.R.anim.linear_interpolator); // default to linear interpolator if (resID > 0) { setInterpolator(context, resID); } setMax(a.getInt(R.styleable.ProgressBar_max, mMax)); setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress)); setSecondaryProgress(a.getInt( R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress)); final Drawable indeterminateDrawable = a.getDrawable( R.styleable.ProgressBar_indeterminateDrawable); if (indeterminateDrawable != null) { if (needsTileify(indeterminateDrawable)) { setIndeterminateDrawableTiled(indeterminateDrawable); } else { setIndeterminateDrawable(indeterminateDrawable); } } mOnlyIndeterminate = a.getBoolean( R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate); mNoInvalidate = false; setIndeterminate(mOnlyIndeterminate || a.getBoolean( R.styleable.ProgressBar_indeterminate, mIndeterminate)); mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl); if (a.hasValue(R.styleable.ProgressBar_progressTintMode)) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mProgressTintMode = Drawable.parseTintMode(a.getInt( R.styleable.ProgressBar_progressTintMode, -1), null); mProgressTintInfo.mHasProgressTintMode = true; } if (a.hasValue(R.styleable.ProgressBar_progressTint)) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mProgressTintList = a.getColorStateList( R.styleable.ProgressBar_progressTint); mProgressTintInfo.mHasProgressTint = true; } if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTintMode)) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mProgressBackgroundTintMode = Drawable.parseTintMode(a.getInt( R.styleable.ProgressBar_progressBackgroundTintMode, -1), null); mProgressTintInfo.mHasProgressBackgroundTintMode = true; } if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTint)) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mProgressBackgroundTintList = a.getColorStateList( R.styleable.ProgressBar_progressBackgroundTint); mProgressTintInfo.mHasProgressBackgroundTint = true; } if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTintMode)) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mSecondaryProgressTintMode = Drawable.parseTintMode( a.getInt(R.styleable.ProgressBar_secondaryProgressTintMode, -1), null); mProgressTintInfo.mHasSecondaryProgressTintMode = true; } if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTint)) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mSecondaryProgressTintList = a.getColorStateList( R.styleable.ProgressBar_secondaryProgressTint); mProgressTintInfo.mHasSecondaryProgressTint = true; } if (a.hasValue(R.styleable.ProgressBar_indeterminateTintMode)) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mIndeterminateTintMode = Drawable.parseTintMode(a.getInt( R.styleable.ProgressBar_indeterminateTintMode, -1), null); mProgressTintInfo.mHasIndeterminateTintMode = true; } if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mIndeterminateTintList = a.getColorStateList( R.styleable.ProgressBar_indeterminateTint); mProgressTintInfo.mHasIndeterminateTint = true; } a.recycle(); applyProgressTints(); applyIndeterminateTint(); // If not explicitly specified this view is important for accessibility. if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } } /** * Returns {@code true} if the target drawable needs to be tileified. * * @param dr the drawable to check * @return {@code true} if the target drawable needs to be tileified, * {@code false} otherwise */ private static boolean needsTileify(Drawable dr) { if (dr instanceof LayerDrawable) { final LayerDrawable orig = (LayerDrawable) dr; final int N = orig.getNumberOfLayers(); for (int i = 0; i < N; i++) { if (needsTileify(orig.getDrawable(i))) { return true; } } return false; } if (dr instanceof StateListDrawable) { final StateListDrawable in = (StateListDrawable) dr; final int N = in.getStateCount(); for (int i = 0; i < N; i++) { if (needsTileify(in.getStateDrawable(i))) { return true; } } return false; } // If there's a bitmap that's not wrapped with a ClipDrawable or // ScaleDrawable, we'll need to wrap it and apply tiling. if (dr instanceof BitmapDrawable) { return true; } return false; } /** * Converts a drawable to a tiled version of itself. It will recursively * traverse layer and state list drawables. */ private Drawable tileify(Drawable drawable, boolean clip) { // TODO: This is a terrible idea that potentially destroys any drawable // that extends any of these classes. We *really* need to remove this. if (drawable instanceof LayerDrawable) { final LayerDrawable orig = (LayerDrawable) drawable; final int N = orig.getNumberOfLayers(); final Drawable[] outDrawables = new Drawable[N]; for (int i = 0; i < N; i++) { final int id = orig.getId(i); outDrawables[i] = tileify(orig.getDrawable(i), (id == R.id.progress || id == R.id.secondaryProgress)); } final LayerDrawable clone = new LayerDrawable(outDrawables); for (int i = 0; i < N; i++) { clone.setId(i, orig.getId(i)); clone.setLayerGravity(i, orig.getLayerGravity(i)); clone.setLayerWidth(i, orig.getLayerWidth(i)); clone.setLayerHeight(i, orig.getLayerHeight(i)); clone.setLayerInsetLeft(i, orig.getLayerInsetLeft(i)); clone.setLayerInsetRight(i, orig.getLayerInsetRight(i)); clone.setLayerInsetTop(i, orig.getLayerInsetTop(i)); clone.setLayerInsetBottom(i, orig.getLayerInsetBottom(i)); clone.setLayerInsetStart(i, orig.getLayerInsetStart(i)); clone.setLayerInsetEnd(i, orig.getLayerInsetEnd(i)); } return clone; } if (drawable instanceof StateListDrawable) { final StateListDrawable in = (StateListDrawable) drawable; final StateListDrawable out = new StateListDrawable(); final int N = in.getStateCount(); for (int i = 0; i < N; i++) { out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip)); } return out; } if (drawable instanceof BitmapDrawable) { final BitmapDrawable bitmap = (BitmapDrawable) drawable; final Bitmap tileBitmap = bitmap.getBitmap(); if (mSampleTile == null) { mSampleTile = tileBitmap; } final BitmapDrawable clone = (BitmapDrawable) bitmap.getConstantState().newDrawable(); clone.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); if (clip) { return new ClipDrawable(clone, Gravity.LEFT, ClipDrawable.HORIZONTAL); } else { return clone; } } return drawable; } Shape getDrawableShape() { final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 }; return new RoundRectShape(roundedCorners, null, null); } /** * Convert a AnimationDrawable for use as a barberpole animation. * Each frame of the animation is wrapped in a ClipDrawable and * given a tiling BitmapShader. */ private Drawable tileifyIndeterminate(Drawable drawable) { if (drawable instanceof AnimationDrawable) { AnimationDrawable background = (AnimationDrawable) drawable; final int N = background.getNumberOfFrames(); AnimationDrawable newBg = new AnimationDrawable(); newBg.setOneShot(background.isOneShot()); for (int i = 0; i < N; i++) { Drawable frame = tileify(background.getFrame(i), true); frame.setLevel(10000); newBg.addFrame(frame, background.getDuration(i)); } newBg.setLevel(10000); drawable = newBg; } return drawable; } /** *

* Initialize the progress bar's default values: *

* */ private void initProgressBar() { mMax = 100; mProgress = 0; mSecondaryProgress = 0; mIndeterminate = false; mOnlyIndeterminate = false; mDuration = 4000; mBehavior = AlphaAnimation.RESTART; mMinWidth = 24; mMaxWidth = 48; mMinHeight = 24; mMaxHeight = 48; } /** *

Indicate whether this progress bar is in indeterminate mode.

* * @return true if the progress bar is in indeterminate mode */ @ViewDebug.ExportedProperty(category = "progress") public synchronized boolean isIndeterminate() { return mIndeterminate; } /** *

Change the indeterminate mode for this progress bar. In indeterminate * mode, the progress is ignored and the progress bar shows an infinite * animation instead.

* * If this progress bar's style only supports indeterminate mode (such as the circular * progress bars), then this will be ignored. * * @param indeterminate true to enable the indeterminate mode */ @android.view.RemotableViewMethod public synchronized void setIndeterminate(boolean indeterminate) { if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) { mIndeterminate = indeterminate; if (indeterminate) { // swap between indeterminate and regular backgrounds swapCurrentDrawable(mIndeterminateDrawable); startAnimation(); } else { swapCurrentDrawable(mProgressDrawable); stopAnimation(); } } } private void swapCurrentDrawable(Drawable newDrawable) { final Drawable oldDrawable = mCurrentDrawable; mCurrentDrawable = newDrawable; if (oldDrawable != mCurrentDrawable) { if (oldDrawable != null) { oldDrawable.setVisible(false, false); } if (mCurrentDrawable != null) { mCurrentDrawable.setVisible(getWindowVisibility() == VISIBLE && isShown(), false); } } } /** *

Get the drawable used to draw the progress bar in * indeterminate mode.

* * @return a {@link android.graphics.drawable.Drawable} instance * * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable) * @see #setIndeterminate(boolean) */ public Drawable getIndeterminateDrawable() { return mIndeterminateDrawable; } /** * Define the drawable used to draw the progress bar in indeterminate mode. * * @param d the new drawable * @see #getIndeterminateDrawable() * @see #setIndeterminate(boolean) */ public void setIndeterminateDrawable(Drawable d) { if (mIndeterminateDrawable != d) { if (mIndeterminateDrawable != null) { mIndeterminateDrawable.setCallback(null); unscheduleDrawable(mIndeterminateDrawable); } mIndeterminateDrawable = d; if (d != null) { d.setCallback(this); d.setLayoutDirection(getLayoutDirection()); if (d.isStateful()) { d.setState(getDrawableState()); } applyIndeterminateTint(); } if (mIndeterminate) { swapCurrentDrawable(d); postInvalidate(); } } } /** * Applies a tint to the indeterminate drawable. Does not modify the * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. *

* Subsequent calls to {@link #setIndeterminateDrawable(Drawable)} will * automatically mutate the drawable and apply the specified tint and * tint mode using * {@link Drawable#setTintList(ColorStateList)}. * * @param tint the tint to apply, may be {@code null} to clear tint * * @attr ref android.R.styleable#ProgressBar_indeterminateTint * @see #getIndeterminateTintList() * @see Drawable#setTintList(ColorStateList) */ @RemotableViewMethod public void setIndeterminateTintList(@Nullable ColorStateList tint) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mIndeterminateTintList = tint; mProgressTintInfo.mHasIndeterminateTint = true; applyIndeterminateTint(); } /** * @return the tint applied to the indeterminate drawable * @attr ref android.R.styleable#ProgressBar_indeterminateTint * @see #setIndeterminateTintList(ColorStateList) */ @Nullable public ColorStateList getIndeterminateTintList() { return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintList : null; } /** * Specifies the blending mode used to apply the tint specified by * {@link #setIndeterminateTintList(ColorStateList)} to the indeterminate * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}. * * @param tintMode the blending mode used to apply the tint, may be * {@code null} to clear tint * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode * @see #setIndeterminateTintList(ColorStateList) * @see Drawable#setTintMode(PorterDuff.Mode) */ public void setIndeterminateTintMode(@Nullable PorterDuff.Mode tintMode) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mIndeterminateTintMode = tintMode; mProgressTintInfo.mHasIndeterminateTintMode = true; applyIndeterminateTint(); } /** * Returns the blending mode used to apply the tint to the indeterminate * drawable, if specified. * * @return the blending mode used to apply the tint to the indeterminate * drawable * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode * @see #setIndeterminateTintMode(PorterDuff.Mode) */ @Nullable public PorterDuff.Mode getIndeterminateTintMode() { return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintMode : null; } private void applyIndeterminateTint() { if (mIndeterminateDrawable != null && mProgressTintInfo != null) { final ProgressTintInfo tintInfo = mProgressTintInfo; if (tintInfo.mHasIndeterminateTint || tintInfo.mHasIndeterminateTintMode) { mIndeterminateDrawable = mIndeterminateDrawable.mutate(); if (tintInfo.mHasIndeterminateTint) { mIndeterminateDrawable.setTintList(tintInfo.mIndeterminateTintList); } if (tintInfo.mHasIndeterminateTintMode) { mIndeterminateDrawable.setTintMode(tintInfo.mIndeterminateTintMode); } // The drawable (or one of its children) may not have been // stateful before applying the tint, so let's try again. if (mIndeterminateDrawable.isStateful()) { mIndeterminateDrawable.setState(getDrawableState()); } } } } /** * Define the tileable drawable used to draw the progress bar in * indeterminate mode. *

* If the drawable is a BitmapDrawable or contains BitmapDrawables, a * tiled copy will be generated for display as a progress bar. * * @param d the new drawable * @see #getIndeterminateDrawable() * @see #setIndeterminate(boolean) */ public void setIndeterminateDrawableTiled(Drawable d) { if (d != null) { d = tileifyIndeterminate(d); } setIndeterminateDrawable(d); } /** *

Get the drawable used to draw the progress bar in * progress mode.

* * @return a {@link android.graphics.drawable.Drawable} instance * * @see #setProgressDrawable(android.graphics.drawable.Drawable) * @see #setIndeterminate(boolean) */ public Drawable getProgressDrawable() { return mProgressDrawable; } /** * Define the drawable used to draw the progress bar in progress mode. * * @param d the new drawable * @see #getProgressDrawable() * @see #setIndeterminate(boolean) */ public void setProgressDrawable(Drawable d) { if (mProgressDrawable != d) { if (mProgressDrawable != null) { mProgressDrawable.setCallback(null); unscheduleDrawable(mProgressDrawable); } mProgressDrawable = d; if (d != null) { d.setCallback(this); d.setLayoutDirection(getLayoutDirection()); if (d.isStateful()) { d.setState(getDrawableState()); } // Make sure the ProgressBar is always tall enough int drawableHeight = d.getMinimumHeight(); if (mMaxHeight < drawableHeight) { mMaxHeight = drawableHeight; requestLayout(); } applyProgressTints(); } if (!mIndeterminate) { swapCurrentDrawable(d); postInvalidate(); } updateDrawableBounds(getWidth(), getHeight()); updateDrawableState(); doRefreshProgress(R.id.progress, mProgress, false, false, false); doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false, false); } } /** * Applies the progress tints in order of increasing specificity. */ private void applyProgressTints() { if (mProgressDrawable != null && mProgressTintInfo != null) { applyPrimaryProgressTint(); applyProgressBackgroundTint(); applySecondaryProgressTint(); } } /** * Should only be called if we've already verified that mProgressDrawable * and mProgressTintInfo are non-null. */ private void applyPrimaryProgressTint() { if (mProgressTintInfo.mHasProgressTint || mProgressTintInfo.mHasProgressTintMode) { final Drawable target = getTintTarget(R.id.progress, true); if (target != null) { if (mProgressTintInfo.mHasProgressTint) { target.setTintList(mProgressTintInfo.mProgressTintList); } if (mProgressTintInfo.mHasProgressTintMode) { target.setTintMode(mProgressTintInfo.mProgressTintMode); } // The drawable (or one of its children) may not have been // stateful before applying the tint, so let's try again. if (target.isStateful()) { target.setState(getDrawableState()); } } } } /** * Should only be called if we've already verified that mProgressDrawable * and mProgressTintInfo are non-null. */ private void applyProgressBackgroundTint() { if (mProgressTintInfo.mHasProgressBackgroundTint || mProgressTintInfo.mHasProgressBackgroundTintMode) { final Drawable target = getTintTarget(R.id.background, false); if (target != null) { if (mProgressTintInfo.mHasProgressBackgroundTint) { target.setTintList(mProgressTintInfo.mProgressBackgroundTintList); } if (mProgressTintInfo.mHasProgressBackgroundTintMode) { target.setTintMode(mProgressTintInfo.mProgressBackgroundTintMode); } // The drawable (or one of its children) may not have been // stateful before applying the tint, so let's try again. if (target.isStateful()) { target.setState(getDrawableState()); } } } } /** * Should only be called if we've already verified that mProgressDrawable * and mProgressTintInfo are non-null. */ private void applySecondaryProgressTint() { if (mProgressTintInfo.mHasSecondaryProgressTint || mProgressTintInfo.mHasSecondaryProgressTintMode) { final Drawable target = getTintTarget(R.id.secondaryProgress, false); if (target != null) { if (mProgressTintInfo.mHasSecondaryProgressTint) { target.setTintList(mProgressTintInfo.mSecondaryProgressTintList); } if (mProgressTintInfo.mHasSecondaryProgressTintMode) { target.setTintMode(mProgressTintInfo.mSecondaryProgressTintMode); } // The drawable (or one of its children) may not have been // stateful before applying the tint, so let's try again. if (target.isStateful()) { target.setState(getDrawableState()); } } } } /** * Applies a tint to the progress indicator, if one exists, or to the * entire progress drawable otherwise. Does not modify the current tint * mode, which is {@link PorterDuff.Mode#SRC_IN} by default. *

* The progress indicator should be specified as a layer with * id {@link android.R.id#progress} in a {@link LayerDrawable} * used as the progress drawable. *

* Subsequent calls to {@link #setProgressDrawable(Drawable)} will * automatically mutate the drawable and apply the specified tint and * tint mode using * {@link Drawable#setTintList(ColorStateList)}. * * @param tint the tint to apply, may be {@code null} to clear tint * * @attr ref android.R.styleable#ProgressBar_progressTint * @see #getProgressTintList() * @see Drawable#setTintList(ColorStateList) */ @RemotableViewMethod public void setProgressTintList(@Nullable ColorStateList tint) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mProgressTintList = tint; mProgressTintInfo.mHasProgressTint = true; if (mProgressDrawable != null) { applyPrimaryProgressTint(); } } /** * Returns the tint applied to the progress drawable, if specified. * * @return the tint applied to the progress drawable * @attr ref android.R.styleable#ProgressBar_progressTint * @see #setProgressTintList(ColorStateList) */ @Nullable public ColorStateList getProgressTintList() { return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintList : null; } /** * Specifies the blending mode used to apply the tint specified by * {@link #setProgressTintList(ColorStateList)}} to the progress * indicator. The default mode is {@link PorterDuff.Mode#SRC_IN}. * * @param tintMode the blending mode used to apply the tint, may be * {@code null} to clear tint * @attr ref android.R.styleable#ProgressBar_progressTintMode * @see #getProgressTintMode() * @see Drawable#setTintMode(PorterDuff.Mode) */ public void setProgressTintMode(@Nullable PorterDuff.Mode tintMode) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mProgressTintMode = tintMode; mProgressTintInfo.mHasProgressTintMode = true; if (mProgressDrawable != null) { applyPrimaryProgressTint(); } } /** * Returns the blending mode used to apply the tint to the progress * drawable, if specified. * * @return the blending mode used to apply the tint to the progress * drawable * @attr ref android.R.styleable#ProgressBar_progressTintMode * @see #setProgressTintMode(PorterDuff.Mode) */ @Nullable public PorterDuff.Mode getProgressTintMode() { return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintMode : null; } /** * Applies a tint to the progress background, if one exists. Does not * modify the current tint mode, which is * {@link PorterDuff.Mode#SRC_ATOP} by default. *

* The progress background must be specified as a layer with * id {@link android.R.id#background} in a {@link LayerDrawable} * used as the progress drawable. *

* Subsequent calls to {@link #setProgressDrawable(Drawable)} where the * drawable contains a progress background will automatically mutate the * drawable and apply the specified tint and tint mode using * {@link Drawable#setTintList(ColorStateList)}. * * @param tint the tint to apply, may be {@code null} to clear tint * * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint * @see #getProgressBackgroundTintList() * @see Drawable#setTintList(ColorStateList) */ @RemotableViewMethod public void setProgressBackgroundTintList(@Nullable ColorStateList tint) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mProgressBackgroundTintList = tint; mProgressTintInfo.mHasProgressBackgroundTint = true; if (mProgressDrawable != null) { applyProgressBackgroundTint(); } } /** * Returns the tint applied to the progress background, if specified. * * @return the tint applied to the progress background * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint * @see #setProgressBackgroundTintList(ColorStateList) */ @Nullable public ColorStateList getProgressBackgroundTintList() { return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintList : null; } /** * Specifies the blending mode used to apply the tint specified by * {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress * background. The default mode is {@link PorterDuff.Mode#SRC_IN}. * * @param tintMode the blending mode used to apply the tint, may be * {@code null} to clear tint * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode * @see #setProgressBackgroundTintList(ColorStateList) * @see Drawable#setTintMode(PorterDuff.Mode) */ public void setProgressBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mProgressBackgroundTintMode = tintMode; mProgressTintInfo.mHasProgressBackgroundTintMode = true; if (mProgressDrawable != null) { applyProgressBackgroundTint(); } } /** * @return the blending mode used to apply the tint to the progress * background * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode * @see #setProgressBackgroundTintMode(PorterDuff.Mode) */ @Nullable public PorterDuff.Mode getProgressBackgroundTintMode() { return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintMode : null; } /** * Applies a tint to the secondary progress indicator, if one exists. * Does not modify the current tint mode, which is * {@link PorterDuff.Mode#SRC_ATOP} by default. *

* The secondary progress indicator must be specified as a layer with * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable} * used as the progress drawable. *

* Subsequent calls to {@link #setProgressDrawable(Drawable)} where the * drawable contains a secondary progress indicator will automatically * mutate the drawable and apply the specified tint and tint mode using * {@link Drawable#setTintList(ColorStateList)}. * * @param tint the tint to apply, may be {@code null} to clear tint * * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint * @see #getSecondaryProgressTintList() * @see Drawable#setTintList(ColorStateList) */ public void setSecondaryProgressTintList(@Nullable ColorStateList tint) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mSecondaryProgressTintList = tint; mProgressTintInfo.mHasSecondaryProgressTint = true; if (mProgressDrawable != null) { applySecondaryProgressTint(); } } /** * Returns the tint applied to the secondary progress drawable, if * specified. * * @return the tint applied to the secondary progress drawable * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint * @see #setSecondaryProgressTintList(ColorStateList) */ @Nullable public ColorStateList getSecondaryProgressTintList() { return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintList : null; } /** * Specifies the blending mode used to apply the tint specified by * {@link #setSecondaryProgressTintList(ColorStateList)}} to the secondary * progress indicator. The default mode is * {@link PorterDuff.Mode#SRC_ATOP}. * * @param tintMode the blending mode used to apply the tint, may be * {@code null} to clear tint * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode * @see #setSecondaryProgressTintList(ColorStateList) * @see Drawable#setTintMode(PorterDuff.Mode) */ public void setSecondaryProgressTintMode(@Nullable PorterDuff.Mode tintMode) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mSecondaryProgressTintMode = tintMode; mProgressTintInfo.mHasSecondaryProgressTintMode = true; if (mProgressDrawable != null) { applySecondaryProgressTint(); } } /** * Returns the blending mode used to apply the tint to the secondary * progress drawable, if specified. * * @return the blending mode used to apply the tint to the secondary * progress drawable * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode * @see #setSecondaryProgressTintMode(PorterDuff.Mode) */ @Nullable public PorterDuff.Mode getSecondaryProgressTintMode() { return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintMode : null; } /** * Returns the drawable to which a tint or tint mode should be applied. * * @param layerId id of the layer to modify * @param shouldFallback whether the base drawable should be returned * if the id does not exist * @return the drawable to modify */ @Nullable private Drawable getTintTarget(int layerId, boolean shouldFallback) { Drawable layer = null; final Drawable d = mProgressDrawable; if (d != null) { mProgressDrawable = d.mutate(); if (d instanceof LayerDrawable) { layer = ((LayerDrawable) d).findDrawableByLayerId(layerId); } if (shouldFallback && layer == null) { layer = d; } } return layer; } /** * Define the tileable drawable used to draw the progress bar in * progress mode. *

* If the drawable is a BitmapDrawable or contains BitmapDrawables, a * tiled copy will be generated for display as a progress bar. * * @param d the new drawable * @see #getProgressDrawable() * @see #setIndeterminate(boolean) */ public void setProgressDrawableTiled(Drawable d) { if (d != null) { d = tileify(d, false); } setProgressDrawable(d); } /** * @return The drawable currently used to draw the progress bar */ Drawable getCurrentDrawable() { return mCurrentDrawable; } @Override protected boolean verifyDrawable(@NonNull Drawable who) { return who == mProgressDrawable || who == mIndeterminateDrawable || super.verifyDrawable(who); } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState(); if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState(); } /** * @hide */ @Override public void onResolveDrawables(int layoutDirection) { final Drawable d = mCurrentDrawable; if (d != null) { d.setLayoutDirection(layoutDirection); } if (mIndeterminateDrawable != null) { mIndeterminateDrawable.setLayoutDirection(layoutDirection); } if (mProgressDrawable != null) { mProgressDrawable.setLayoutDirection(layoutDirection); } } @Override public void postInvalidate() { if (!mNoInvalidate) { super.postInvalidate(); } } private class RefreshProgressRunnable implements Runnable { public void run() { synchronized (ProgressBar.this) { final int count = mRefreshData.size(); for (int i = 0; i < count; i++) { final RefreshData rd = mRefreshData.get(i); doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate); rd.recycle(); } mRefreshData.clear(); mRefreshIsPosted = false; } } } private static class RefreshData { private static final int POOL_MAX = 24; private static final SynchronizedPool sPool = new SynchronizedPool(POOL_MAX); public int id; public int progress; public boolean fromUser; public boolean animate; public static RefreshData obtain(int id, int progress, boolean fromUser, boolean animate) { RefreshData rd = sPool.acquire(); if (rd == null) { rd = new RefreshData(); } rd.id = id; rd.progress = progress; rd.fromUser = fromUser; rd.animate = animate; return rd; } public void recycle() { sPool.release(this); } } private synchronized void doRefreshProgress(int id, int progress, boolean fromUser, boolean callBackToApp, boolean animate) { final float scale = mMax > 0 ? progress / (float) mMax : 0; final boolean isPrimary = id == R.id.progress; if (isPrimary && animate) { final ObjectAnimator animator = ObjectAnimator.ofFloat(this, VISUAL_PROGRESS, scale); animator.setAutoCancel(true); animator.setDuration(PROGRESS_ANIM_DURATION); animator.setInterpolator(PROGRESS_ANIM_INTERPOLATOR); animator.start(); } else { setVisualProgress(id, scale); } if (isPrimary && callBackToApp) { onProgressRefresh(scale, fromUser, progress); } } void onProgressRefresh(float scale, boolean fromUser, int progress) { if (AccessibilityManager.getInstance(mContext).isEnabled()) { scheduleAccessibilityEventSender(); } } /** * Sets the visual state of a progress indicator. * * @param id the identifier of the progress indicator * @param progress the visual progress in the range [0...1] */ private void setVisualProgress(int id, float progress) { mVisualProgress = progress; Drawable d = mCurrentDrawable; if (d instanceof LayerDrawable) { d = ((LayerDrawable) d).findDrawableByLayerId(id); if (d == null) { // If we can't find the requested layer, fall back to setting // the level of the entire drawable. This will break if // progress is set on multiple elements, but the theme-default // drawable will always have all layer IDs present. d = mCurrentDrawable; } } if (d != null) { final int level = (int) (progress * MAX_LEVEL); d.setLevel(level); } else { invalidate(); } onVisualProgressChanged(id, progress); } /** * Called when the visual state of a progress indicator changes. * * @param id the identifier of the progress indicator * @param progress the visual progress in the range [0...1] */ void onVisualProgressChanged(int id, float progress) { // Stub method. } private synchronized void refreshProgress(int id, int progress, boolean fromUser, boolean animate) { if (mUiThreadId == Thread.currentThread().getId()) { doRefreshProgress(id, progress, fromUser, true, animate); } else { if (mRefreshProgressRunnable == null) { mRefreshProgressRunnable = new RefreshProgressRunnable(); } final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate); mRefreshData.add(rd); if (mAttached && !mRefreshIsPosted) { post(mRefreshProgressRunnable); mRefreshIsPosted = true; } } } /** * Sets the current progress to the specified value. Does not do anything * if the progress bar is in indeterminate mode. *

* This method will immediately update the visual position of the progress * indicator. To animate the visual position to the target value, use * {@link #setProgress(int, boolean)}}. * * @param progress the new progress, between 0 and {@link #getMax()} * * @see #setIndeterminate(boolean) * @see #isIndeterminate() * @see #getProgress() * @see #incrementProgressBy(int) */ @android.view.RemotableViewMethod public synchronized void setProgress(int progress) { setProgressInternal(progress, false, false); } /** * Sets the current progress to the specified value, optionally animating * the visual position between the current and target values. *

* Animation does not affect the result of {@link #getProgress()}, which * will return the target value immediately after this method is called. * * @param progress the new progress value, between 0 and {@link #getMax()} * @param animate {@code true} to animate between the current and target * values or {@code false} to not animate */ public void setProgress(int progress, boolean animate) { setProgressInternal(progress, false, animate); } @android.view.RemotableViewMethod synchronized boolean setProgressInternal(int progress, boolean fromUser, boolean animate) { if (mIndeterminate) { // Not applicable. return false; } progress = MathUtils.constrain(progress, 0, mMax); if (progress == mProgress) { // No change from current. return false; } mProgress = progress; refreshProgress(R.id.progress, mProgress, fromUser, animate); return true; } /** *

* Set the current secondary progress to the specified value. Does not do * anything if the progress bar is in indeterminate mode. *

* * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()} * @see #setIndeterminate(boolean) * @see #isIndeterminate() * @see #getSecondaryProgress() * @see #incrementSecondaryProgressBy(int) */ @android.view.RemotableViewMethod public synchronized void setSecondaryProgress(int secondaryProgress) { if (mIndeterminate) { return; } if (secondaryProgress < 0) { secondaryProgress = 0; } if (secondaryProgress > mMax) { secondaryProgress = mMax; } if (secondaryProgress != mSecondaryProgress) { mSecondaryProgress = secondaryProgress; refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false); } } /** *

Get the progress bar's current level of progress. Return 0 when the * progress bar is in indeterminate mode.

* * @return the current progress, between 0 and {@link #getMax()} * * @see #setIndeterminate(boolean) * @see #isIndeterminate() * @see #setProgress(int) * @see #setMax(int) * @see #getMax() */ @ViewDebug.ExportedProperty(category = "progress") public synchronized int getProgress() { return mIndeterminate ? 0 : mProgress; } /** *

Get the progress bar's current level of secondary progress. Return 0 when the * progress bar is in indeterminate mode.

* * @return the current secondary progress, between 0 and {@link #getMax()} * * @see #setIndeterminate(boolean) * @see #isIndeterminate() * @see #setSecondaryProgress(int) * @see #setMax(int) * @see #getMax() */ @ViewDebug.ExportedProperty(category = "progress") public synchronized int getSecondaryProgress() { return mIndeterminate ? 0 : mSecondaryProgress; } /** *

Return the upper limit of this progress bar's range.

* * @return a positive integer * * @see #setMax(int) * @see #getProgress() * @see #getSecondaryProgress() */ @ViewDebug.ExportedProperty(category = "progress") public synchronized int getMax() { return mMax; } /** *

Set the range of the progress bar to 0...max.

* * @param max the upper range of this progress bar * * @see #getMax() * @see #setProgress(int) * @see #setSecondaryProgress(int) */ @android.view.RemotableViewMethod public synchronized void setMax(int max) { if (max < 0) { max = 0; } if (max != mMax) { mMax = max; postInvalidate(); if (mProgress > max) { mProgress = max; } refreshProgress(R.id.progress, mProgress, false, false); } } /** *

Increase the progress bar's progress by the specified amount.

* * @param diff the amount by which the progress must be increased * * @see #setProgress(int) */ public synchronized final void incrementProgressBy(int diff) { setProgress(mProgress + diff); } /** *

Increase the progress bar's secondary progress by the specified amount.

* * @param diff the amount by which the secondary progress must be increased * * @see #setSecondaryProgress(int) */ public synchronized final void incrementSecondaryProgressBy(int diff) { setSecondaryProgress(mSecondaryProgress + diff); } /** *

Start the indeterminate progress animation.

*/ void startAnimation() { if (getVisibility() != VISIBLE || getWindowVisibility() != VISIBLE) { return; } if (mIndeterminateDrawable instanceof Animatable) { mShouldStartAnimationDrawable = true; mHasAnimation = false; } else { mHasAnimation = true; if (mInterpolator == null) { mInterpolator = new LinearInterpolator(); } if (mTransformation == null) { mTransformation = new Transformation(); } else { mTransformation.clear(); } if (mAnimation == null) { mAnimation = new AlphaAnimation(0.0f, 1.0f); } else { mAnimation.reset(); } mAnimation.setRepeatMode(mBehavior); mAnimation.setRepeatCount(Animation.INFINITE); mAnimation.setDuration(mDuration); mAnimation.setInterpolator(mInterpolator); mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME); } postInvalidate(); } /** *

Stop the indeterminate progress animation.

*/ void stopAnimation() { mHasAnimation = false; if (mIndeterminateDrawable instanceof Animatable) { ((Animatable) mIndeterminateDrawable).stop(); mShouldStartAnimationDrawable = false; } postInvalidate(); } /** * Sets the acceleration curve for the indeterminate animation. * The interpolator is loaded as a resource from the specified context. * * @param context The application environment * @param resID The resource identifier of the interpolator to load */ public void setInterpolator(Context context, @InterpolatorRes int resID) { setInterpolator(AnimationUtils.loadInterpolator(context, resID)); } /** * Sets the acceleration curve for the indeterminate animation. * Defaults to a linear interpolation. * * @param interpolator The interpolator which defines the acceleration curve */ public void setInterpolator(Interpolator interpolator) { mInterpolator = interpolator; } /** * Gets the acceleration curve type for the indeterminate animation. * * @return the {@link Interpolator} associated to this animation */ public Interpolator getInterpolator() { return mInterpolator; } @Override public void onVisibilityAggregated(boolean isVisible) { super.onVisibilityAggregated(isVisible); if (isVisible != mAggregatedIsVisible) { mAggregatedIsVisible = isVisible; if (mIndeterminate) { // let's be nice with the UI thread if (isVisible) { startAnimation(); } else { stopAnimation(); } } if (mCurrentDrawable != null) { mCurrentDrawable.setVisible(isVisible, false); } } } @Override public void invalidateDrawable(@NonNull Drawable dr) { if (!mInDrawing) { if (verifyDrawable(dr)) { final Rect dirty = dr.getBounds(); final int scrollX = mScrollX + mPaddingLeft; final int scrollY = mScrollY + mPaddingTop; invalidate(dirty.left + scrollX, dirty.top + scrollY, dirty.right + scrollX, dirty.bottom + scrollY); } else { super.invalidateDrawable(dr); } } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { updateDrawableBounds(w, h); } private void updateDrawableBounds(int w, int h) { // onDraw will translate the canvas so we draw starting at 0,0. // Subtract out padding for the purposes of the calculations below. w -= mPaddingRight + mPaddingLeft; h -= mPaddingTop + mPaddingBottom; int right = w; int bottom = h; int top = 0; int left = 0; if (mIndeterminateDrawable != null) { // Aspect ratio logic does not apply to AnimationDrawables if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) { // Maintain aspect ratio. Certain kinds of animated drawables // get very confused otherwise. final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth(); final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight(); final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight; final float boundAspect = (float) w / h; if (intrinsicAspect != boundAspect) { if (boundAspect > intrinsicAspect) { // New width is larger. Make it smaller to match height. final int width = (int) (h * intrinsicAspect); left = (w - width) / 2; right = left + width; } else { // New height is larger. Make it smaller to match width. final int height = (int) (w * (1 / intrinsicAspect)); top = (h - height) / 2; bottom = top + height; } } } if (isLayoutRtl() && mMirrorForRtl) { int tempLeft = left; left = w - right; right = w - tempLeft; } mIndeterminateDrawable.setBounds(left, top, right, bottom); } if (mProgressDrawable != null) { mProgressDrawable.setBounds(0, 0, right, bottom); } } @Override protected synchronized void onDraw(Canvas canvas) { super.onDraw(canvas); drawTrack(canvas); } /** * Draws the progress bar track. */ void drawTrack(Canvas canvas) { final Drawable d = mCurrentDrawable; if (d != null) { // Translate canvas so a indeterminate circular progress bar with padding // rotates properly in its animation final int saveCount = canvas.save(); if (isLayoutRtl() && mMirrorForRtl) { canvas.translate(getWidth() - mPaddingRight, mPaddingTop); canvas.scale(-1.0f, 1.0f); } else { canvas.translate(mPaddingLeft, mPaddingTop); } final long time = getDrawingTime(); if (mHasAnimation) { mAnimation.getTransformation(time, mTransformation); final float scale = mTransformation.getAlpha(); try { mInDrawing = true; d.setLevel((int) (scale * MAX_LEVEL)); } finally { mInDrawing = false; } postInvalidateOnAnimation(); } d.draw(canvas); canvas.restoreToCount(saveCount); if (mShouldStartAnimationDrawable && d instanceof Animatable) { ((Animatable) d).start(); mShouldStartAnimationDrawable = false; } } } @Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int dw = 0; int dh = 0; final Drawable d = mCurrentDrawable; if (d != null) { dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); } updateDrawableState(); dw += mPaddingLeft + mPaddingRight; dh += mPaddingTop + mPaddingBottom; final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0); final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0); setMeasuredDimension(measuredWidth, measuredHeight); } @Override protected void drawableStateChanged() { super.drawableStateChanged(); updateDrawableState(); } private void updateDrawableState() { final int[] state = getDrawableState(); boolean changed = false; final Drawable progressDrawable = mProgressDrawable; if (progressDrawable != null && progressDrawable.isStateful()) { changed |= progressDrawable.setState(state); } final Drawable indeterminateDrawable = mIndeterminateDrawable; if (indeterminateDrawable != null && indeterminateDrawable.isStateful()) { changed |= indeterminateDrawable.setState(state); } if (changed) { invalidate(); } } @Override public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); if (mProgressDrawable != null) { mProgressDrawable.setHotspot(x, y); } if (mIndeterminateDrawable != null) { mIndeterminateDrawable.setHotspot(x, y); } } static class SavedState extends BaseSavedState { int progress; int secondaryProgress; /** * Constructor called from {@link ProgressBar#onSaveInstanceState()} */ SavedState(Parcelable superState) { super(superState); } /** * Constructor called from {@link #CREATOR} */ private SavedState(Parcel in) { super(in); progress = in.readInt(); secondaryProgress = in.readInt(); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(progress); out.writeInt(secondaryProgress); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } @Override public Parcelable onSaveInstanceState() { // Force our ancestor class to save its state Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); ss.progress = mProgress; ss.secondaryProgress = mSecondaryProgress; return ss; } @Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); setProgress(ss.progress); setSecondaryProgress(ss.secondaryProgress); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (mIndeterminate) { startAnimation(); } if (mRefreshData != null) { synchronized (this) { final int count = mRefreshData.size(); for (int i = 0; i < count; i++) { final RefreshData rd = mRefreshData.get(i); doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate); rd.recycle(); } mRefreshData.clear(); } } mAttached = true; } @Override protected void onDetachedFromWindow() { if (mIndeterminate) { stopAnimation(); } if (mRefreshProgressRunnable != null) { removeCallbacks(mRefreshProgressRunnable); mRefreshIsPosted = false; } if (mAccessibilityEventSender != null) { removeCallbacks(mAccessibilityEventSender); } // This should come after stopAnimation(), otherwise an invalidate message remains in the // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation super.onDetachedFromWindow(); mAttached = false; } @Override public CharSequence getAccessibilityClassName() { return ProgressBar.class.getName(); } /** @hide */ @Override public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { super.onInitializeAccessibilityEventInternal(event); event.setItemCount(mMax); event.setCurrentItemIndex(mProgress); } /** @hide */ @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); if (!isIndeterminate()) { AccessibilityNodeInfo.RangeInfo rangeInfo = AccessibilityNodeInfo.RangeInfo.obtain( AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT, 0, getMax(), getProgress()); info.setRangeInfo(rangeInfo); } } /** * Schedule a command for sending an accessibility event. *
* Note: A command is used to ensure that accessibility events * are sent at most one in a given time frame to save * system resources while the progress changes quickly. */ private void scheduleAccessibilityEventSender() { if (mAccessibilityEventSender == null) { mAccessibilityEventSender = new AccessibilityEventSender(); } else { removeCallbacks(mAccessibilityEventSender); } postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT); } /** @hide */ @Override protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { super.encodeProperties(stream); stream.addProperty("progress:max", getMax()); stream.addProperty("progress:progress", getProgress()); stream.addProperty("progress:secondaryProgress", getSecondaryProgress()); stream.addProperty("progress:indeterminate", isIndeterminate()); } /** * Command for sending an accessibility event. */ private class AccessibilityEventSender implements Runnable { public void run() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); } } private static class ProgressTintInfo { ColorStateList mIndeterminateTintList; PorterDuff.Mode mIndeterminateTintMode; boolean mHasIndeterminateTint; boolean mHasIndeterminateTintMode; ColorStateList mProgressTintList; PorterDuff.Mode mProgressTintMode; boolean mHasProgressTint; boolean mHasProgressTintMode; ColorStateList mProgressBackgroundTintList; PorterDuff.Mode mProgressBackgroundTintMode; boolean mHasProgressBackgroundTint; boolean mHasProgressBackgroundTintMode; ColorStateList mSecondaryProgressTintList; PorterDuff.Mode mSecondaryProgressTintMode; boolean mHasSecondaryProgressTint; boolean mHasSecondaryProgressTintMode; } /** * Property wrapper around the visual state of the {@code progress} functionality * handled by the {@link ProgressBar#setProgress(int, boolean)} method. This does * not correspond directly to the actual progress -- only the visual state. */ private final FloatProperty VISUAL_PROGRESS = new FloatProperty("visual_progress") { @Override public void setValue(ProgressBar object, float value) { object.setVisualProgress(R.id.progress, value); object.mVisualProgress = value; } @Override public Float get(ProgressBar object) { return object.mVisualProgress; } }; }