/* * 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.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.annotation.InterpolatorRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Px; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.BlendMode; 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.Build; 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.view.inspector.InspectableProperty; import android.widget.RemoteViews.RemoteView; import com.android.internal.R; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Locale; /** *
* A user interface element that indicates the progress of an operation. * Progress bar supports two modes to represent progress: determinate, and indeterminate. For * a visual overview of the difference between determinate and indeterminate progress modes, see * * Progress & activity. * Display progress bars to a user in a non-interruptive way. * Show the progress bar in your app's user interface or in a notification * instead of within a dialog. *
** Use indeterminate mode for the progress bar when you do not know how long an * operation will take. * Indeterminate mode is the default for progress bar and shows a cyclic animation without a * specific amount of progress indicated. * The following example shows an indeterminate progress bar: *
* <ProgressBar * android:id="@+id/indeterminateBar" * android:layout_width="wrap_content" * android:layout_height="wrap_content" * /> ** *
* Use determinate mode for the progress bar when you want to show that a specific quantity of * progress has occurred. * For example, the percent remaining of a file being retrieved, the amount records in * a batch written to database, or the percent remaining of an audio file that is playing. *
*
* To indicate determinate progress, you set the style of the progress bar to * {@link android.R.style#Widget_ProgressBar_Horizontal} and set the amount of progress. * The following example shows a determinate progress bar that is 25% complete: *
* <ProgressBar * android:id="@+id/determinateBar" * style="@android:style/Widget.ProgressBar.Horizontal" * android:layout_width="wrap_content" * android:layout_height="wrap_content" * android:progress="25"/> ** You can update the percentage of progress displayed by using the * {@link #setProgress(int)} method, or by calling * {@link #incrementProgressBy(int)} to increase the current progress completed * by a specified amount. * By default, the progress bar is full when the progress value reaches 100. * You can adjust this default by setting the * {@link android.R.styleable#ProgressBar_max android:max} attribute. * *
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_min * @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; /** 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; /** * Outside the framework, please use {@link ProgressBar#getMinWidth()} and * {@link ProgressBar#setMinWidth(int)} instead of accessing these directly. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) int mMinWidth; int mMaxWidth; /** * Outside the framework, please use {@link ProgressBar#getMinHeight()} and * {@link ProgressBar#setMinHeight(int)} instead of accessing these directly. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) int mMinHeight; /** * Outside the framework, please use {@link ProgressBar#getMaxHeight()} ()} and * {@link ProgressBar#setMaxHeight(int)} (int)} instead of accessing these directly. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) int mMaxHeight; private int mProgress; private int mSecondaryProgress; private int mMin; private boolean mMinInitialized; private int mMax; private boolean mMaxInitialized; private int mBehavior; // Better to define a Drawable that implements Animatable if you want to modify animation // characteristics programatically. @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124052713) private int mDuration; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) private boolean mIndeterminate; @UnsupportedAppUsage(trackingBug = 124049927) private boolean mOnlyIndeterminate; private Transformation mTransformation; private AlphaAnimation mAnimation; private boolean mHasAnimation; private Drawable mIndeterminateDrawable; private Drawable mProgressDrawable; /** * Outside the framework, instead of accessing this directly, please use * {@link #getCurrentDrawable()}, {@link #setProgressDrawable(Drawable)}, * {@link #setIndeterminateDrawable(Drawable)} and their tiled versions. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) private Drawable mCurrentDrawable; private ProgressTintInfo mProgressTintInfo; int mSampleWidth = 0; 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; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) boolean mMirrorForRtl = false; private boolean mAggregatedIsVisible; private CharSequence mCustomStateDescription = null; private final ArrayList* Initialize the progress bar's default values: *
*Indicate whether this progress bar is in indeterminate mode.
* * @return true if the progress bar is in indeterminate mode */ @InspectableProperty @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) */ @InspectableProperty public Drawable getIndeterminateDrawable() { return mIndeterminateDrawable; } /** * Define the drawable used to draw the progress bar in indeterminate mode. * *For the Drawable to animate, it must implement {@link Animatable}, or override * {@link Drawable#onLevelChange(int)}. A Drawable that implements Animatable will be animated * via that interface and therefore provides the greatest amount of customization. A Drawable * that only overrides onLevelChange(int) is animated directly by ProgressBar and only the * animation {@link android.R.styleable#ProgressBar_indeterminateDuration duration}, * {@link android.R.styleable#ProgressBar_indeterminateBehavior repeating behavior}, and * {@link #setInterpolator(Interpolator) interpolator} can be modified, and only before the * indeterminate animation begins. * * @param d the new drawable * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable * @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) */ @InspectableProperty(name = "indeterminateTint") @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) { setIndeterminateTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : 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 blendMode 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#setTintBlendMode(BlendMode) */ @RemotableViewMethod public void setIndeterminateTintBlendMode(@Nullable BlendMode blendMode) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mIndeterminateBlendMode = blendMode; 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) */ @InspectableProperty @Nullable public PorterDuff.Mode getIndeterminateTintMode() { BlendMode mode = getIndeterminateTintBlendMode(); return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; } /** * 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 #setIndeterminateTintBlendMode(BlendMode) */ @InspectableProperty(attributeId = R.styleable.ProgressBar_indeterminateTintMode) @Nullable public BlendMode getIndeterminateTintBlendMode() { return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateBlendMode : 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.setTintBlendMode(tintInfo.mIndeterminateBlendMode); } // 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) */ @InspectableProperty 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); } } /** * @hide */ @InspectableProperty public boolean getMirrorForRtl() { return mMirrorForRtl; } /** * 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.setTintBlendMode(mProgressTintInfo.mProgressBlendMode); } // 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.setTintBlendMode(mProgressTintInfo.mProgressBackgroundBlendMode); } // 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.setTintBlendMode(mProgressTintInfo.mSecondaryProgressBlendMode); } // 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) */ @InspectableProperty(name = "progressTint") @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) { setProgressTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : 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 blendMode 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#setTintBlendMode(BlendMode) */ @RemotableViewMethod public void setProgressTintBlendMode(@Nullable BlendMode blendMode) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mProgressBlendMode = blendMode; 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) */ @InspectableProperty @Nullable public PorterDuff.Mode getProgressTintMode() { BlendMode mode = getProgressTintBlendMode(); return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; } /** * 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 #setProgressTintBlendMode(BlendMode) */ @InspectableProperty(attributeId = android.R.styleable.ProgressBar_progressTintMode) @Nullable public BlendMode getProgressTintBlendMode() { return mProgressTintInfo != null ? mProgressTintInfo.mProgressBlendMode : 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) */ @InspectableProperty(name = "progressBackgroundTint") @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) { setProgressBackgroundTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null); } /** * Specifies the blending mode used to apply the tint specified by * {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress * background. The default mode is {@link BlendMode#SRC_IN}. * * @param blendMode 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#setTintBlendMode(BlendMode) */ @RemotableViewMethod public void setProgressBackgroundTintBlendMode(@Nullable BlendMode blendMode) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mProgressBackgroundBlendMode = blendMode; 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) */ @InspectableProperty @Nullable public PorterDuff.Mode getProgressBackgroundTintMode() { BlendMode mode = getProgressBackgroundTintBlendMode(); return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; } /** * @return the blending mode used to apply the tint to the progress * background * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode * @see #setProgressBackgroundTintBlendMode(BlendMode) */ @InspectableProperty(attributeId = R.styleable.ProgressBar_progressBackgroundTintMode) @Nullable public BlendMode getProgressBackgroundTintBlendMode() { return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundBlendMode : 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) */ @RemotableViewMethod 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) */ @InspectableProperty(name = "secondaryProgressTint") @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) { setSecondaryProgressTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : 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 blendMode 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#setTintBlendMode(BlendMode) */ @RemotableViewMethod public void setSecondaryProgressTintBlendMode(@Nullable BlendMode blendMode) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); } mProgressTintInfo.mSecondaryProgressBlendMode = blendMode; 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) */ @InspectableProperty @Nullable public PorterDuff.Mode getSecondaryProgressTintMode() { BlendMode mode = getSecondaryProgressTintBlendMode(); return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; } /** * 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 #setSecondaryProgressTintBlendMode(BlendMode) */ @InspectableProperty(attributeId = android.R.styleable.ProgressBar_secondaryProgressTintMode) @Nullable public BlendMode getSecondaryProgressTintBlendMode() { return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressBlendMode : 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);
}
/**
* Returns the drawable currently used to draw the progress bar. This will be
* either {@link #getProgressDrawable()} or {@link #getIndeterminateDrawable()}
* depending on whether the progress bar is in determinate or indeterminate mode.
*
* @return the drawable currently used to draw the progress bar
*/
@Nullable
public 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
* 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 {@link #getMin()} 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 {@link #getMin()} 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
@UnsupportedAppUsage
synchronized boolean setProgressInternal(int progress, boolean fromUser, boolean animate) {
if (mIndeterminate) {
// Not applicable.
return false;
}
progress = MathUtils.constrain(progress, mMin, 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.
* Get the progress bar's current level of progress. Return 0 when the
* progress bar is in indeterminate mode. Get the progress bar's current level of secondary progress. Return 0 when the
* progress bar is in indeterminate mode. Return the lower limit of this progress bar's range. Return the upper limit of this progress bar's range. Set the lower range of the progress bar to min. Set the upper range of the progress bar max. Increase the progress bar's progress by the specified amount. Increase the progress bar's secondary progress by the specified amount. Start the indeterminate progress animation. Stop the indeterminate progress animation. The interpolator is loaded as a resource from the specified context. Defaults to a linear
* interpolation.
*
* The interpolator only affects the indeterminate animation if the
* {@link #setIndeterminateDrawable(Drawable) supplied indeterminate drawable} does not
* implement {@link Animatable}.
*
* This call must be made before the indeterminate animation starts for it to have an affect.
*
* @param context The application environment
* @param resID The resource identifier of the interpolator to load
* @attr ref android.R.styleable#ProgressBar_interpolator
* @see #setInterpolator(Interpolator)
* @see #getInterpolator()
*/
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.
*
* The interpolator only affects the indeterminate animation if the
* {@link #setIndeterminateDrawable(Drawable) supplied indeterminate drawable} does not
* implement {@link Animatable}.
*
* This call must be made before the indeterminate animation starts for it to have
* an affect.
*
* @param interpolator The interpolator which defines the acceleration curve
* @attr ref android.R.styleable#ProgressBar_interpolator
* @see #setInterpolator(Context, int)
* @see #getInterpolator()
*/
public void setInterpolator(Interpolator interpolator) {
mInterpolator = interpolator;
}
/**
* Gets the acceleration curve type for the indeterminate animation.
*
* @return the {@link Interpolator} associated to this animation
* @attr ref android.R.styleable#ProgressBar_interpolator
* @see #setInterpolator(Context, int)
* @see #setInterpolator(Interpolator)
*/
@InspectableProperty
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 @android.annotation.NonNull Parcelable.Creator