| /* |
| * 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; |
| |
| /** |
| * <p> |
| * 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 |
| * <a href="https://material.io/guidelines/components/progress-activity.html#progress-activity-types-of-indicators"> |
| * Progress & activity</a>. |
| * 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. |
| * </p> |
| * <h3>Indeterminate Progress</h3> |
| * <p> |
| * 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: |
| * <pre> |
| * <ProgressBar |
| * android:id="@+id/indeterminateBar" |
| * android:layout_width="wrap_content" |
| * android:layout_height="wrap_content" |
| * /> |
| * </pre> |
| * </p> |
| * <h3>Determinate Progress</h3> |
| * <p> |
| * 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. |
| * <p> |
| * <p> |
| * 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: |
| * <pre> |
| * <ProgressBar |
| * android:id="@+id/determinateBar" |
| * style="@android:style/Widget.ProgressBar.Horizontal" |
| * android:layout_width="wrap_content" |
| * android:layout_height="wrap_content" |
| * android:progress="25"/> |
| * </pre> |
| * 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. |
| * </p> |
| * <p>Other progress bar styles provided by the system include:</p> |
| * <ul> |
| * <li>{@link android.R.style#Widget_ProgressBar_Horizontal Widget.ProgressBar.Horizontal}</li> |
| * <li>{@link android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}</li> |
| * <li>{@link android.R.style#Widget_ProgressBar_Large Widget.ProgressBar.Large}</li> |
| * <li>{@link android.R.style#Widget_ProgressBar_Inverse Widget.ProgressBar.Inverse}</li> |
| * <li>{@link android.R.style#Widget_ProgressBar_Small_Inverse |
| * Widget.ProgressBar.Small.Inverse}</li> |
| * <li>{@link android.R.style#Widget_ProgressBar_Large_Inverse |
| * Widget.ProgressBar.Large.Inverse}</li> |
| * </ul> |
| * <p>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).</p> |
| * |
| * <p><strong>XML attributes</b></strong> |
| * <p> |
| * See {@link android.R.styleable#ProgressBar ProgressBar Attributes}, |
| * {@link android.R.styleable#View View Attributes} |
| * </p> |
| * |
| * @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 final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>(); |
| |
| private ObjectAnimator mLastProgressAnimator; |
| |
| private NumberFormat mPercentFormat; |
| private Locale mCachedLocale; |
| |
| /** |
| * 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); |
| saveAttributeDataForStyleable(context, R.styleable.ProgressBar, |
| attrs, a, 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); |
| } |
| |
| setMin(a.getInt(R.styleable.ProgressBar_min, mMin)); |
| 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.mProgressBlendMode = Drawable.parseBlendMode(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.mProgressBackgroundBlendMode = Drawable.parseBlendMode(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.mSecondaryProgressBlendMode = Drawable.parseBlendMode( |
| 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.mIndeterminateBlendMode = Drawable.parseBlendMode(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); |
| } |
| } |
| |
| /** |
| * Sets the minimum width the progress bar can have. |
| * @param minWidth the minimum width to be set, in pixels |
| * @attr ref android.R.styleable#ProgressBar_minWidth |
| */ |
| public void setMinWidth(@Px int minWidth) { |
| mMinWidth = minWidth; |
| requestLayout(); |
| } |
| |
| /** |
| * @return the minimum width the progress bar can have, in pixels |
| */ |
| @Px public int getMinWidth() { |
| return mMinWidth; |
| } |
| |
| /** |
| * Sets the maximum width the progress bar can have. |
| * @param maxWidth the maximum width to be set, in pixels |
| * @attr ref android.R.styleable#ProgressBar_maxWidth |
| */ |
| public void setMaxWidth(@Px int maxWidth) { |
| mMaxWidth = maxWidth; |
| requestLayout(); |
| } |
| |
| /** |
| * @return the maximum width the progress bar can have, in pixels |
| */ |
| @Px public int getMaxWidth() { |
| return mMaxWidth; |
| } |
| |
| /** |
| * Sets the minimum height the progress bar can have. |
| * @param minHeight the minimum height to be set, in pixels |
| * @attr ref android.R.styleable#ProgressBar_minHeight |
| */ |
| public void setMinHeight(@Px int minHeight) { |
| mMinHeight = minHeight; |
| requestLayout(); |
| } |
| |
| /** |
| * @return the minimum height the progress bar can have, in pixels |
| */ |
| @Px public int getMinHeight() { |
| return mMinHeight; |
| } |
| |
| /** |
| * Sets the maximum height the progress bar can have. |
| * @param maxHeight the maximum height to be set, in pixels |
| * @attr ref android.R.styleable#ProgressBar_maxHeight |
| */ |
| public void setMaxHeight(@Px int maxHeight) { |
| mMaxHeight = maxHeight; |
| requestLayout(); |
| } |
| |
| /** |
| * @return the maximum height the progress bar can have, in pixels |
| */ |
| @Px public int getMaxHeight() { |
| return mMaxHeight; |
| } |
| |
| /** |
| * 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. |
| */ |
| @UnsupportedAppUsage |
| 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 Drawable.ConstantState cs = drawable.getConstantState(); |
| final BitmapDrawable clone = (BitmapDrawable) cs.newDrawable(getResources()); |
| clone.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); |
| |
| if (mSampleWidth <= 0) { |
| mSampleWidth = clone.getIntrinsicWidth(); |
| } |
| |
| 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; |
| } |
| |
| /** |
| * <p> |
| * Initialize the progress bar's default values: |
| * </p> |
| * <ul> |
| * <li>progress = 0</li> |
| * <li>max = 100</li> |
| * <li>animation duration = 4000 ms</li> |
| * <li>indeterminate = false</li> |
| * <li>behavior = repeat</li> |
| * </ul> |
| */ |
| private void initProgressBar() { |
| mMin = 0; |
| mMax = 100; |
| mProgress = 0; |
| mSecondaryProgress = 0; |
| mIndeterminate = false; |
| mOnlyIndeterminate = false; |
| mDuration = 4000; |
| mBehavior = AlphaAnimation.RESTART; |
| mMinWidth = 24; |
| mMaxWidth = 48; |
| mMinHeight = 24; |
| mMaxHeight = 48; |
| } |
| |
| /** |
| * <p>Indicate whether this progress bar is in indeterminate mode.</p> |
| * |
| * @return true if the progress bar is in indeterminate mode |
| */ |
| @InspectableProperty |
| @ViewDebug.ExportedProperty(category = "progress") |
| public synchronized boolean isIndeterminate() { |
| return mIndeterminate; |
| } |
| |
| /** |
| * <p>Change the indeterminate mode for this progress bar. In indeterminate |
| * mode, the progress is ignored and the progress bar shows an infinite |
| * animation instead.</p> |
| * |
| * 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(); |
| } |
| |
| notifyViewAccessibilityStateChangedIfNeeded( |
| AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); |
| } |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| /** |
| * <p>Get the drawable used to draw the progress bar in |
| * indeterminate mode.</p> |
| * |
| * @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. |
| * |
| * <p>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. |
| * <p> |
| * 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. |
| * <p> |
| * 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); |
| } |
| |
| /** |
| * <p>Get the drawable used to draw the progress bar in |
| * progress mode.</p> |
| * |
| * @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. |
| * <p> |
| * 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. |
| * <p> |
| * 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. |
| * <p> |
| * 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. |
| * <p> |
| * 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. |
| * <p> |
| * 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. |
| * <p> |
| * 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. |
| * <p> |
| * 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<RefreshData> sPool = |
| new SynchronizedPool<RefreshData>(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) { |
| int range = mMax - mMin; |
| final float scale = range > 0 ? (progress - mMin) / (float) range : 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.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| mLastProgressAnimator = null; |
| } |
| }); |
| animator.start(); |
| mLastProgressAnimator = animator; |
| } else { |
| if (isPrimary && mLastProgressAnimator != null) { |
| mLastProgressAnimator.cancel(); |
| mLastProgressAnimator = null; |
| } |
| setVisualProgress(id, scale); |
| } |
| |
| if (isPrimary && callBackToApp) { |
| onProgressRefresh(scale, fromUser, progress); |
| } |
| } |
| |
| private float getPercent(int progress) { |
| final float maxProgress = getMax(); |
| final float minProgress = getMin(); |
| final float currentProgress = progress; |
| final float diffProgress = maxProgress - minProgress; |
| if (diffProgress <= 0.0f) { |
| return 0.0f; |
| } |
| final float percent = (currentProgress - minProgress) / diffProgress; |
| return Math.max(0.0f, Math.min(1.0f, percent)); |
| } |
| |
| /** |
| * Default percentage format of the state description based on progress, for example, |
| * "50 percent". |
| * |
| * @param progress the progress value, between {@link #getMin()} and {@link #getMax()} |
| * @return state description based on progress |
| */ |
| private CharSequence formatStateDescription(int progress) { |
| // Cache the locale-appropriate NumberFormat. Configuration locale is guaranteed |
| // non-null, so the first time this is called we will always get the appropriate |
| // NumberFormat, then never regenerate it unless the locale changes on the fly. |
| final Locale curLocale = mContext.getResources().getConfiguration().getLocales().get(0); |
| if (!curLocale.equals(mCachedLocale)) { |
| mCachedLocale = curLocale; |
| mPercentFormat = NumberFormat.getPercentInstance(curLocale); |
| } |
| return mPercentFormat.format(getPercent(progress)); |
| } |
| |
| /** |
| * This function is called when an instance or subclass sets the state description. Once this |
| * is called and the argument is not null, the app developer will be responsible for updating |
| * state description when progress changes and the default state description will not be used. |
| * App developers can restore the default behavior by setting the argument to null. If set |
| * progress is called first and then setStateDescription is called, two state change events |
| * will be merged by event throttling and we can still get the correct state description. |
| * |
| * @param stateDescription The state description. |
| */ |
| @Override |
| @RemotableViewMethod |
| public void setStateDescription(@Nullable CharSequence stateDescription) { |
| // Assume the previous custom state description is different from default state description. |
| // Otherwise when the argument is null to restore the default state description, we will |
| // send out a state description changed event even though the state description presented to |
| // the user doesn't change. Since mStateDescription in View is private, we can't prevent |
| // this event from sending out. |
| super.setStateDescription(stateDescription); |
| } |
| |
| void onProgressRefresh(float scale, boolean fromUser, int progress) { |
| if (AccessibilityManager.getInstance(mContext).isEnabled() |
| && getStateDescription() == null && !isIndeterminate()) { |
| AccessibilityEvent event = AccessibilityEvent.obtain(); |
| event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); |
| event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION); |
| sendAccessibilityEventUnchecked(event); |
| } |
| } |
| |
| /** |
| * 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. |
| } |
| |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) |
| 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. |
| * <p> |
| * 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. |
| * <p> |
| * 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; |
| } |
| |
| /** |
| * <p> |
| * Set the current secondary progress to the specified value. Does not do |
| * anything if the progress bar is in indeterminate mode. |
| * </p> |
| * |
| * @param secondaryProgress the new secondary progress, between {@link #getMin()} 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 < mMin) { |
| secondaryProgress = mMin; |
| } |
| |
| if (secondaryProgress > mMax) { |
| secondaryProgress = mMax; |
| } |
| |
| if (secondaryProgress != mSecondaryProgress) { |
| mSecondaryProgress = secondaryProgress; |
| refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false); |
| } |
| } |
| |
| /** |
| * <p>Get the progress bar's current level of progress. Return 0 when the |
| * progress bar is in indeterminate mode.</p> |
| * |
| * @return the current progress, between {@link #getMin()} and {@link #getMax()} |
| * |
| * @see #setIndeterminate(boolean) |
| * @see #isIndeterminate() |
| * @see #setProgress(int) |
| * @see #setMax(int) |
| * @see #getMax() |
| */ |
| @ViewDebug.ExportedProperty(category = "progress") |
| @InspectableProperty |
| public synchronized int getProgress() { |
| return mIndeterminate ? 0 : mProgress; |
| } |
| |
| /** |
| * <p>Get the progress bar's current level of secondary progress. Return 0 when the |
| * progress bar is in indeterminate mode.</p> |
| * |
| * @return the current secondary progress, between {@link #getMin()} and {@link #getMax()} |
| * |
| * @see #setIndeterminate(boolean) |
| * @see #isIndeterminate() |
| * @see #setSecondaryProgress(int) |
| * @see #setMax(int) |
| * @see #getMax() |
| */ |
| @ViewDebug.ExportedProperty(category = "progress") |
| @InspectableProperty |
| public synchronized int getSecondaryProgress() { |
| return mIndeterminate ? 0 : mSecondaryProgress; |
| } |
| |
| /** |
| * <p>Return the lower limit of this progress bar's range.</p> |
| * |
| * @return a positive integer |
| * |
| * @see #setMin(int) |
| * @see #getProgress() |
| * @see #getSecondaryProgress() |
| */ |
| @ViewDebug.ExportedProperty(category = "progress") |
| @InspectableProperty |
| public synchronized int getMin() { |
| return mMin; |
| } |
| |
| /** |
| * <p>Return the upper limit of this progress bar's range.</p> |
| * |
| * @return a positive integer |
| * |
| * @see #setMax(int) |
| * @see #getProgress() |
| * @see #getSecondaryProgress() |
| */ |
| @ViewDebug.ExportedProperty(category = "progress") |
| @InspectableProperty |
| public synchronized int getMax() { |
| return mMax; |
| } |
| |
| /** |
| * <p>Set the lower range of the progress bar to <tt>min</tt>.</p> |
| * |
| * @param min the lower range of this progress bar |
| * |
| * @see #getMin() |
| * @see #setProgress(int) |
| * @see #setSecondaryProgress(int) |
| */ |
| @android.view.RemotableViewMethod |
| public synchronized void setMin(int min) { |
| if (mMaxInitialized) { |
| if (min > mMax) { |
| min = mMax; |
| } |
| } |
| mMinInitialized = true; |
| if (mMaxInitialized && min != mMin) { |
| mMin = min; |
| postInvalidate(); |
| |
| if (mProgress < min) { |
| mProgress = min; |
| } |
| refreshProgress(R.id.progress, mProgress, false, false); |
| } else { |
| mMin = min; |
| } |
| } |
| |
| /** |
| * <p>Set the upper range of the progress bar <tt>max</tt>.</p> |
| * |
| * @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 (mMinInitialized) { |
| if (max < mMin) { |
| max = mMin; |
| } |
| } |
| mMaxInitialized = true; |
| if (mMinInitialized && max != mMax) { |
| mMax = max; |
| postInvalidate(); |
| |
| if (mProgress > max) { |
| mProgress = max; |
| } |
| refreshProgress(R.id.progress, mProgress, false, false); |
| } else { |
| mMax = max; |
| } |
| } |
| |
| /** |
| * <p>Increase the progress bar's progress by the specified amount.</p> |
| * |
| * @param diff the amount by which the progress must be increased |
| * |
| * @see #setProgress(int) |
| */ |
| public synchronized final void incrementProgressBy(int diff) { |
| setProgress(mProgress + diff); |
| } |
| |
| /** |
| * <p>Increase the progress bar's secondary progress by the specified amount.</p> |
| * |
| * @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); |
| } |
| |
| /** |
| * <p>Start the indeterminate progress animation.</p> |
| */ |
| @UnsupportedAppUsage |
| 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(); |
| } |
| |
| /** |
| * <p>Stop the indeterminate progress animation.</p> |
| */ |
| @UnsupportedAppUsage |
| void stopAnimation() { |
| mHasAnimation = false; |
| if (mIndeterminateDrawable instanceof Animatable) { |
| ((Animatable) mIndeterminateDrawable).stop(); |
| mShouldStartAnimationDrawable = false; |
| } |
| postInvalidate(); |
| } |
| |
| /** |
| * Sets the acceleration curve for the indeterminate animation. |
| * |
| * <p>The interpolator is loaded as a resource from the specified context. Defaults to a linear |
| * interpolation. |
| * |
| * <p>The interpolator only affects the indeterminate animation if the |
| * {@link #setIndeterminateDrawable(Drawable) supplied indeterminate drawable} does not |
| * implement {@link Animatable}. |
| * |
| * <p>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. |
| * |
| * <p>The interpolator only affects the indeterminate animation if the |
| * {@link #setIndeterminateDrawable(Drawable) supplied indeterminate drawable} does not |
| * implement {@link Animatable}. |
| * |
| * <p>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<SavedState> CREATOR |
| = new Parcelable.Creator<SavedState>() { |
| 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; |
| } |
| // 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 - mMin); |
| 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, getMin(), getMax(), |
| getProgress()); |
| info.setRangeInfo(rangeInfo); |
| } |
| |
| // Only set the default state description when custom state descripton is null. |
| if (getStateDescription() == null) { |
| if (isIndeterminate()) { |
| info.setStateDescription(getResources().getString(R.string.in_progress)); |
| } else { |
| info.setStateDescription(formatStateDescription(mProgress)); |
| } |
| } |
| } |
| |
| /** @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()); |
| } |
| |
| /** |
| * Returns whether the ProgressBar is animating or not. This is essentially the same |
| * as whether the ProgressBar is {@link #isIndeterminate() indeterminate} and visible, |
| * as indeterminate ProgressBars are always animating, and non-indeterminate |
| * ProgressBars are not animating. |
| * |
| * @return true if the ProgressBar is animating, false otherwise. |
| */ |
| public boolean isAnimating() { |
| return isIndeterminate() && getWindowVisibility() == VISIBLE && isShown(); |
| } |
| |
| private static class ProgressTintInfo { |
| ColorStateList mIndeterminateTintList; |
| BlendMode mIndeterminateBlendMode; |
| boolean mHasIndeterminateTint; |
| boolean mHasIndeterminateTintMode; |
| |
| ColorStateList mProgressTintList; |
| BlendMode mProgressBlendMode; |
| boolean mHasProgressTint; |
| boolean mHasProgressTintMode; |
| |
| ColorStateList mProgressBackgroundTintList; |
| BlendMode mProgressBackgroundBlendMode; |
| boolean mHasProgressBackgroundTint; |
| boolean mHasProgressBackgroundTintMode; |
| |
| ColorStateList mSecondaryProgressTintList; |
| BlendMode mSecondaryProgressBlendMode; |
| 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<ProgressBar> VISUAL_PROGRESS = |
| new FloatProperty<ProgressBar>("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; |
| } |
| }; |
| } |