blob: 1f379c94cc03a3680aadea00cb4750b842dc5994 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.widget;
import android.animation.ObjectAnimator;
import android.annotation.InterpolatorRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.StateListDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.graphics.drawable.shapes.Shape;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.MathUtils;
import android.util.Pools.SynchronizedPool;
import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewHierarchyEncoder;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import android.widget.RemoteViews.RemoteView;
import com.android.internal.R;
import java.util.ArrayList;
/**
* <p>
* Visual indicator of progress in some operation. Displays a bar to the user
* representing how far the operation has progressed; the application can
* change the amount of progress (modifying the length of the bar) as it moves
* forward. There is also a secondary progress displayable on a progress bar
* which is useful for displaying intermediate progress, such as the buffer
* level during a streaming playback progress bar.
* </p>
*
* <p>
* A progress bar can also be made indeterminate. In indeterminate mode, the
* progress bar shows a cyclic animation without an indication of progress. This mode is used by
* applications when the length of the task is unknown. The indeterminate progress bar can be either
* a spinning wheel or a horizontal bar.
* </p>
*
* <p>The following code example shows how a progress bar can be used from
* a worker thread to update the user interface to notify the user of progress:
* </p>
*
* <pre>
* public class MyActivity extends Activity {
* private static final int PROGRESS = 0x1;
*
* private ProgressBar mProgress;
* private int mProgressStatus = 0;
*
* private Handler mHandler = new Handler();
*
* protected void onCreate(Bundle icicle) {
* super.onCreate(icicle);
*
* setContentView(R.layout.progressbar_activity);
*
* mProgress = (ProgressBar) findViewById(R.id.progress_bar);
*
* // Start lengthy operation in a background thread
* new Thread(new Runnable() {
* public void run() {
* while (mProgressStatus &lt; 100) {
* mProgressStatus = doWork();
*
* // Update the progress bar
* mHandler.post(new Runnable() {
* public void run() {
* mProgress.setProgress(mProgressStatus);
* }
* });
* }
* }
* }).start();
* }
* }</pre>
*
* <p>To add a progress bar to a layout file, you can use the {@code <ProgressBar>} element.
* By default, the progress bar is a spinning wheel (an indeterminate indicator). To change to a
* horizontal progress bar, apply the {@link android.R.style#Widget_ProgressBar_Horizontal
* Widget.ProgressBar.Horizontal} style, like so:</p>
*
* <pre>
* &lt;ProgressBar
* style="@android:style/Widget.ProgressBar.Horizontal"
* ... /&gt;</pre>
*
* <p>If you will use the progress bar to show real progress, you must use the horizontal bar. You
* can then increment the progress with {@link #incrementProgressBy incrementProgressBy()} or
* {@link #setProgress setProgress()}. By default, the progress bar is full when it reaches 100. If
* necessary, you can adjust the maximum value (the value for a full bar) using the {@link
* android.R.styleable#ProgressBar_max android:max} attribute. Other attributes available are listed
* below.</p>
*
* <p>Another common style to apply to the progress bar is {@link
* android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}, which shows a smaller
* version of the spinning wheel&mdash;useful when waiting for content to load.
* For example, you can insert this kind of progress bar into your default layout for
* a view that will be populated by some content fetched from the Internet&mdash;the spinning wheel
* appears immediately and when your application receives the content, it replaces the progress bar
* with the loaded content. For example:</p>
*
* <pre>
* &lt;LinearLayout
* android:orientation="horizontal"
* ... &gt;
* &lt;ProgressBar
* android:layout_width="wrap_content"
* android:layout_height="wrap_content"
* style="@android:style/Widget.ProgressBar.Small"
* android:layout_marginRight="5dp" /&gt;
* &lt;TextView
* android:layout_width="wrap_content"
* android:layout_height="wrap_content"
* android:text="@string/loading" /&gt;
* &lt;/LinearLayout&gt;</pre>
*
* <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_max
* @attr ref android.R.styleable#ProgressBar_maxHeight
* @attr ref android.R.styleable#ProgressBar_maxWidth
* @attr ref android.R.styleable#ProgressBar_minHeight
* @attr ref android.R.styleable#ProgressBar_minWidth
* @attr ref android.R.styleable#ProgressBar_mirrorForRtl
* @attr ref android.R.styleable#ProgressBar_progress
* @attr ref android.R.styleable#ProgressBar_progressDrawable
* @attr ref android.R.styleable#ProgressBar_secondaryProgress
*/
@RemoteView
public class ProgressBar extends View {
private static final int MAX_LEVEL = 10000;
private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200;
/** Interpolator used for smooth progress animations. */
private static final DecelerateInterpolator PROGRESS_ANIM_INTERPOLATOR =
new DecelerateInterpolator();
/** Duration of smooth progress animations. */
private static final int PROGRESS_ANIM_DURATION = 80;
int mMinWidth;
int mMaxWidth;
int mMinHeight;
int mMaxHeight;
private int mProgress;
private int mSecondaryProgress;
private int mMax;
private int mBehavior;
private int mDuration;
private boolean mIndeterminate;
private boolean mOnlyIndeterminate;
private Transformation mTransformation;
private AlphaAnimation mAnimation;
private boolean mHasAnimation;
private Drawable mIndeterminateDrawable;
private Drawable mProgressDrawable;
private Drawable mCurrentDrawable;
private ProgressTintInfo mProgressTintInfo;
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;
boolean mMirrorForRtl = false;
private boolean mAggregatedIsVisible;
private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>();
private AccessibilityEventSender mAccessibilityEventSender;
/**
* Create a new progress bar with range 0...100 and initial progress of 0.
* @param context the application environment
*/
public ProgressBar(Context context) {
this(context, null);
}
public ProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.progressBarStyle);
}
public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mUiThreadId = Thread.currentThread().getId();
initProgressBar();
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes);
mNoInvalidate = true;
final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
if (progressDrawable != null) {
// Calling setProgressDrawable can set mMaxHeight, so make sure the
// corresponding XML attribute for mMaxHeight is read after calling
// this method.
if (needsTileify(progressDrawable)) {
setProgressDrawableTiled(progressDrawable);
} else {
setProgressDrawable(progressDrawable);
}
}
mDuration = a.getInt(R.styleable.ProgressBar_indeterminateDuration, mDuration);
mMinWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_minWidth, mMinWidth);
mMaxWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_maxWidth, mMaxWidth);
mMinHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_minHeight, mMinHeight);
mMaxHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_maxHeight, mMaxHeight);
mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior);
final int resID = a.getResourceId(
com.android.internal.R.styleable.ProgressBar_interpolator,
android.R.anim.linear_interpolator); // default to linear interpolator
if (resID > 0) {
setInterpolator(context, resID);
}
setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
setSecondaryProgress(a.getInt(
R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
final Drawable indeterminateDrawable = a.getDrawable(
R.styleable.ProgressBar_indeterminateDrawable);
if (indeterminateDrawable != null) {
if (needsTileify(indeterminateDrawable)) {
setIndeterminateDrawableTiled(indeterminateDrawable);
} else {
setIndeterminateDrawable(indeterminateDrawable);
}
}
mOnlyIndeterminate = a.getBoolean(
R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate);
mNoInvalidate = false;
setIndeterminate(mOnlyIndeterminate || a.getBoolean(
R.styleable.ProgressBar_indeterminate, mIndeterminate));
mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl);
if (a.hasValue(R.styleable.ProgressBar_progressTintMode)) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mProgressTintMode = Drawable.parseTintMode(a.getInt(
R.styleable.ProgressBar_progressTintMode, -1), null);
mProgressTintInfo.mHasProgressTintMode = true;
}
if (a.hasValue(R.styleable.ProgressBar_progressTint)) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mProgressTintList = a.getColorStateList(
R.styleable.ProgressBar_progressTint);
mProgressTintInfo.mHasProgressTint = true;
}
if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTintMode)) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mProgressBackgroundTintMode = Drawable.parseTintMode(a.getInt(
R.styleable.ProgressBar_progressBackgroundTintMode, -1), null);
mProgressTintInfo.mHasProgressBackgroundTintMode = true;
}
if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTint)) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mProgressBackgroundTintList = a.getColorStateList(
R.styleable.ProgressBar_progressBackgroundTint);
mProgressTintInfo.mHasProgressBackgroundTint = true;
}
if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTintMode)) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mSecondaryProgressTintMode = Drawable.parseTintMode(
a.getInt(R.styleable.ProgressBar_secondaryProgressTintMode, -1), null);
mProgressTintInfo.mHasSecondaryProgressTintMode = true;
}
if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTint)) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mSecondaryProgressTintList = a.getColorStateList(
R.styleable.ProgressBar_secondaryProgressTint);
mProgressTintInfo.mHasSecondaryProgressTint = true;
}
if (a.hasValue(R.styleable.ProgressBar_indeterminateTintMode)) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mIndeterminateTintMode = Drawable.parseTintMode(a.getInt(
R.styleable.ProgressBar_indeterminateTintMode, -1), null);
mProgressTintInfo.mHasIndeterminateTintMode = true;
}
if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mIndeterminateTintList = a.getColorStateList(
R.styleable.ProgressBar_indeterminateTint);
mProgressTintInfo.mHasIndeterminateTint = true;
}
a.recycle();
applyProgressTints();
applyIndeterminateTint();
// If not explicitly specified this view is important for accessibility.
if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
}
/**
* Returns {@code true} if the target drawable needs to be tileified.
*
* @param dr the drawable to check
* @return {@code true} if the target drawable needs to be tileified,
* {@code false} otherwise
*/
private static boolean needsTileify(Drawable dr) {
if (dr instanceof LayerDrawable) {
final LayerDrawable orig = (LayerDrawable) dr;
final int N = orig.getNumberOfLayers();
for (int i = 0; i < N; i++) {
if (needsTileify(orig.getDrawable(i))) {
return true;
}
}
return false;
}
if (dr instanceof StateListDrawable) {
final StateListDrawable in = (StateListDrawable) dr;
final int N = in.getStateCount();
for (int i = 0; i < N; i++) {
if (needsTileify(in.getStateDrawable(i))) {
return true;
}
}
return false;
}
// If there's a bitmap that's not wrapped with a ClipDrawable or
// ScaleDrawable, we'll need to wrap it and apply tiling.
if (dr instanceof BitmapDrawable) {
return true;
}
return false;
}
/**
* Converts a drawable to a tiled version of itself. It will recursively
* traverse layer and state list drawables.
*/
private Drawable tileify(Drawable drawable, boolean clip) {
// TODO: This is a terrible idea that potentially destroys any drawable
// that extends any of these classes. We *really* need to remove this.
if (drawable instanceof LayerDrawable) {
final LayerDrawable orig = (LayerDrawable) drawable;
final int N = orig.getNumberOfLayers();
final Drawable[] outDrawables = new Drawable[N];
for (int i = 0; i < N; i++) {
final int id = orig.getId(i);
outDrawables[i] = tileify(orig.getDrawable(i),
(id == R.id.progress || id == R.id.secondaryProgress));
}
final LayerDrawable clone = new LayerDrawable(outDrawables);
for (int i = 0; i < N; i++) {
clone.setId(i, orig.getId(i));
clone.setLayerGravity(i, orig.getLayerGravity(i));
clone.setLayerWidth(i, orig.getLayerWidth(i));
clone.setLayerHeight(i, orig.getLayerHeight(i));
clone.setLayerInsetLeft(i, orig.getLayerInsetLeft(i));
clone.setLayerInsetRight(i, orig.getLayerInsetRight(i));
clone.setLayerInsetTop(i, orig.getLayerInsetTop(i));
clone.setLayerInsetBottom(i, orig.getLayerInsetBottom(i));
clone.setLayerInsetStart(i, orig.getLayerInsetStart(i));
clone.setLayerInsetEnd(i, orig.getLayerInsetEnd(i));
}
return clone;
}
if (drawable instanceof StateListDrawable) {
final StateListDrawable in = (StateListDrawable) drawable;
final StateListDrawable out = new StateListDrawable();
final int N = in.getStateCount();
for (int i = 0; i < N; i++) {
out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
}
return out;
}
if (drawable instanceof BitmapDrawable) {
final 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() {
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
*/
@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();
}
}
}
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)
*/
public Drawable getIndeterminateDrawable() {
return mIndeterminateDrawable;
}
/**
* Define the drawable used to draw the progress bar in indeterminate mode.
*
* @param d the new drawable
* @see #getIndeterminateDrawable()
* @see #setIndeterminate(boolean)
*/
public void setIndeterminateDrawable(Drawable d) {
if (mIndeterminateDrawable != d) {
if (mIndeterminateDrawable != null) {
mIndeterminateDrawable.setCallback(null);
unscheduleDrawable(mIndeterminateDrawable);
}
mIndeterminateDrawable = d;
if (d != null) {
d.setCallback(this);
d.setLayoutDirection(getLayoutDirection());
if (d.isStateful()) {
d.setState(getDrawableState());
}
applyIndeterminateTint();
}
if (mIndeterminate) {
swapCurrentDrawable(d);
postInvalidate();
}
}
}
/**
* Applies a tint to the indeterminate drawable. Does not modify the
* current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
* <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)
*/
@Nullable
public ColorStateList getIndeterminateTintList() {
return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintList : null;
}
/**
* Specifies the blending mode used to apply the tint specified by
* {@link #setIndeterminateTintList(ColorStateList)} to the indeterminate
* drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
*
* @param tintMode the blending mode used to apply the tint, may be
* {@code null} to clear tint
* @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
* @see #setIndeterminateTintList(ColorStateList)
* @see Drawable#setTintMode(PorterDuff.Mode)
*/
public void setIndeterminateTintMode(@Nullable PorterDuff.Mode tintMode) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mIndeterminateTintMode = tintMode;
mProgressTintInfo.mHasIndeterminateTintMode = true;
applyIndeterminateTint();
}
/**
* Returns the blending mode used to apply the tint to the indeterminate
* drawable, if specified.
*
* @return the blending mode used to apply the tint to the indeterminate
* drawable
* @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
* @see #setIndeterminateTintMode(PorterDuff.Mode)
*/
@Nullable
public PorterDuff.Mode getIndeterminateTintMode() {
return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintMode : null;
}
private void applyIndeterminateTint() {
if (mIndeterminateDrawable != null && mProgressTintInfo != null) {
final ProgressTintInfo tintInfo = mProgressTintInfo;
if (tintInfo.mHasIndeterminateTint || tintInfo.mHasIndeterminateTintMode) {
mIndeterminateDrawable = mIndeterminateDrawable.mutate();
if (tintInfo.mHasIndeterminateTint) {
mIndeterminateDrawable.setTintList(tintInfo.mIndeterminateTintList);
}
if (tintInfo.mHasIndeterminateTintMode) {
mIndeterminateDrawable.setTintMode(tintInfo.mIndeterminateTintMode);
}
// The drawable (or one of its children) may not have been
// stateful before applying the tint, so let's try again.
if (mIndeterminateDrawable.isStateful()) {
mIndeterminateDrawable.setState(getDrawableState());
}
}
}
}
/**
* Define the tileable drawable used to draw the progress bar in
* indeterminate mode.
* <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)
*/
public Drawable getProgressDrawable() {
return mProgressDrawable;
}
/**
* Define the drawable used to draw the progress bar in progress mode.
*
* @param d the new drawable
* @see #getProgressDrawable()
* @see #setIndeterminate(boolean)
*/
public void setProgressDrawable(Drawable d) {
if (mProgressDrawable != d) {
if (mProgressDrawable != null) {
mProgressDrawable.setCallback(null);
unscheduleDrawable(mProgressDrawable);
}
mProgressDrawable = d;
if (d != null) {
d.setCallback(this);
d.setLayoutDirection(getLayoutDirection());
if (d.isStateful()) {
d.setState(getDrawableState());
}
// Make sure the ProgressBar is always tall enough
int drawableHeight = d.getMinimumHeight();
if (mMaxHeight < drawableHeight) {
mMaxHeight = drawableHeight;
requestLayout();
}
applyProgressTints();
}
if (!mIndeterminate) {
swapCurrentDrawable(d);
postInvalidate();
}
updateDrawableBounds(getWidth(), getHeight());
updateDrawableState();
doRefreshProgress(R.id.progress, mProgress, false, false, false);
doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false, false);
}
}
/**
* Applies the progress tints in order of increasing specificity.
*/
private void applyProgressTints() {
if (mProgressDrawable != null && mProgressTintInfo != null) {
applyPrimaryProgressTint();
applyProgressBackgroundTint();
applySecondaryProgressTint();
}
}
/**
* Should only be called if we've already verified that mProgressDrawable
* and mProgressTintInfo are non-null.
*/
private void applyPrimaryProgressTint() {
if (mProgressTintInfo.mHasProgressTint
|| mProgressTintInfo.mHasProgressTintMode) {
final Drawable target = getTintTarget(R.id.progress, true);
if (target != null) {
if (mProgressTintInfo.mHasProgressTint) {
target.setTintList(mProgressTintInfo.mProgressTintList);
}
if (mProgressTintInfo.mHasProgressTintMode) {
target.setTintMode(mProgressTintInfo.mProgressTintMode);
}
// The drawable (or one of its children) may not have been
// stateful before applying the tint, so let's try again.
if (target.isStateful()) {
target.setState(getDrawableState());
}
}
}
}
/**
* Should only be called if we've already verified that mProgressDrawable
* and mProgressTintInfo are non-null.
*/
private void applyProgressBackgroundTint() {
if (mProgressTintInfo.mHasProgressBackgroundTint
|| mProgressTintInfo.mHasProgressBackgroundTintMode) {
final Drawable target = getTintTarget(R.id.background, false);
if (target != null) {
if (mProgressTintInfo.mHasProgressBackgroundTint) {
target.setTintList(mProgressTintInfo.mProgressBackgroundTintList);
}
if (mProgressTintInfo.mHasProgressBackgroundTintMode) {
target.setTintMode(mProgressTintInfo.mProgressBackgroundTintMode);
}
// The drawable (or one of its children) may not have been
// stateful before applying the tint, so let's try again.
if (target.isStateful()) {
target.setState(getDrawableState());
}
}
}
}
/**
* Should only be called if we've already verified that mProgressDrawable
* and mProgressTintInfo are non-null.
*/
private void applySecondaryProgressTint() {
if (mProgressTintInfo.mHasSecondaryProgressTint
|| mProgressTintInfo.mHasSecondaryProgressTintMode) {
final Drawable target = getTintTarget(R.id.secondaryProgress, false);
if (target != null) {
if (mProgressTintInfo.mHasSecondaryProgressTint) {
target.setTintList(mProgressTintInfo.mSecondaryProgressTintList);
}
if (mProgressTintInfo.mHasSecondaryProgressTintMode) {
target.setTintMode(mProgressTintInfo.mSecondaryProgressTintMode);
}
// The drawable (or one of its children) may not have been
// stateful before applying the tint, so let's try again.
if (target.isStateful()) {
target.setState(getDrawableState());
}
}
}
}
/**
* Applies a tint to the progress indicator, if one exists, or to the
* entire progress drawable otherwise. Does not modify the current tint
* mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
* <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)
*/
@Nullable
public ColorStateList getProgressTintList() {
return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintList : null;
}
/**
* Specifies the blending mode used to apply the tint specified by
* {@link #setProgressTintList(ColorStateList)}} to the progress
* indicator. The default mode is {@link PorterDuff.Mode#SRC_IN}.
*
* @param tintMode the blending mode used to apply the tint, may be
* {@code null} to clear tint
* @attr ref android.R.styleable#ProgressBar_progressTintMode
* @see #getProgressTintMode()
* @see Drawable#setTintMode(PorterDuff.Mode)
*/
public void setProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mProgressTintMode = tintMode;
mProgressTintInfo.mHasProgressTintMode = true;
if (mProgressDrawable != null) {
applyPrimaryProgressTint();
}
}
/**
* Returns the blending mode used to apply the tint to the progress
* drawable, if specified.
*
* @return the blending mode used to apply the tint to the progress
* drawable
* @attr ref android.R.styleable#ProgressBar_progressTintMode
* @see #setProgressTintMode(PorterDuff.Mode)
*/
@Nullable
public PorterDuff.Mode getProgressTintMode() {
return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintMode : null;
}
/**
* Applies a tint to the progress background, if one exists. Does not
* modify the current tint mode, which is
* {@link PorterDuff.Mode#SRC_ATOP} by default.
* <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)
*/
@Nullable
public ColorStateList getProgressBackgroundTintList() {
return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintList : null;
}
/**
* Specifies the blending mode used to apply the tint specified by
* {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress
* background. The default mode is {@link PorterDuff.Mode#SRC_IN}.
*
* @param tintMode the blending mode used to apply the tint, may be
* {@code null} to clear tint
* @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
* @see #setProgressBackgroundTintList(ColorStateList)
* @see Drawable#setTintMode(PorterDuff.Mode)
*/
public void setProgressBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mProgressBackgroundTintMode = tintMode;
mProgressTintInfo.mHasProgressBackgroundTintMode = true;
if (mProgressDrawable != null) {
applyProgressBackgroundTint();
}
}
/**
* @return the blending mode used to apply the tint to the progress
* background
* @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
* @see #setProgressBackgroundTintMode(PorterDuff.Mode)
*/
@Nullable
public PorterDuff.Mode getProgressBackgroundTintMode() {
return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintMode : null;
}
/**
* Applies a tint to the secondary progress indicator, if one exists.
* Does not modify the current tint mode, which is
* {@link PorterDuff.Mode#SRC_ATOP} by default.
* <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)
*/
public void setSecondaryProgressTintList(@Nullable ColorStateList tint) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mSecondaryProgressTintList = tint;
mProgressTintInfo.mHasSecondaryProgressTint = true;
if (mProgressDrawable != null) {
applySecondaryProgressTint();
}
}
/**
* Returns the tint applied to the secondary progress drawable, if
* specified.
*
* @return the tint applied to the secondary progress drawable
* @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
* @see #setSecondaryProgressTintList(ColorStateList)
*/
@Nullable
public ColorStateList getSecondaryProgressTintList() {
return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintList : null;
}
/**
* Specifies the blending mode used to apply the tint specified by
* {@link #setSecondaryProgressTintList(ColorStateList)}} to the secondary
* progress indicator. The default mode is
* {@link PorterDuff.Mode#SRC_ATOP}.
*
* @param tintMode the blending mode used to apply the tint, may be
* {@code null} to clear tint
* @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
* @see #setSecondaryProgressTintList(ColorStateList)
* @see Drawable#setTintMode(PorterDuff.Mode)
*/
public void setSecondaryProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mSecondaryProgressTintMode = tintMode;
mProgressTintInfo.mHasSecondaryProgressTintMode = true;
if (mProgressDrawable != null) {
applySecondaryProgressTint();
}
}
/**
* Returns the blending mode used to apply the tint to the secondary
* progress drawable, if specified.
*
* @return the blending mode used to apply the tint to the secondary
* progress drawable
* @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
* @see #setSecondaryProgressTintMode(PorterDuff.Mode)
*/
@Nullable
public PorterDuff.Mode getSecondaryProgressTintMode() {
return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintMode : null;
}
/**
* Returns the drawable to which a tint or tint mode should be applied.
*
* @param layerId id of the layer to modify
* @param shouldFallback whether the base drawable should be returned
* if the id does not exist
* @return the drawable to modify
*/
@Nullable
private Drawable getTintTarget(int layerId, boolean shouldFallback) {
Drawable layer = null;
final Drawable d = mProgressDrawable;
if (d != null) {
mProgressDrawable = d.mutate();
if (d instanceof LayerDrawable) {
layer = ((LayerDrawable) d).findDrawableByLayerId(layerId);
}
if (shouldFallback && layer == null) {
layer = d;
}
}
return layer;
}
/**
* Define the tileable drawable used to draw the progress bar in
* progress mode.
* <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);
}
/**
* @return The drawable currently used to draw the progress bar
*/
Drawable getCurrentDrawable() {
return mCurrentDrawable;
}
@Override
protected boolean verifyDrawable(@NonNull Drawable who) {
return who == mProgressDrawable || who == mIndeterminateDrawable
|| super.verifyDrawable(who);
}
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
}
/**
* @hide
*/
@Override
public void onResolveDrawables(int layoutDirection) {
final Drawable d = mCurrentDrawable;
if (d != null) {
d.setLayoutDirection(layoutDirection);
}
if (mIndeterminateDrawable != null) {
mIndeterminateDrawable.setLayoutDirection(layoutDirection);
}
if (mProgressDrawable != null) {
mProgressDrawable.setLayoutDirection(layoutDirection);
}
}
@Override
public void postInvalidate() {
if (!mNoInvalidate) {
super.postInvalidate();
}
}
private class RefreshProgressRunnable implements Runnable {
public void run() {
synchronized (ProgressBar.this) {
final int count = mRefreshData.size();
for (int i = 0; i < count; i++) {
final RefreshData rd = mRefreshData.get(i);
doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate);
rd.recycle();
}
mRefreshData.clear();
mRefreshIsPosted = false;
}
}
}
private static class RefreshData {
private static final int POOL_MAX = 24;
private static final SynchronizedPool<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) {
final float scale = mMax > 0 ? progress / (float) mMax : 0;
final boolean isPrimary = id == R.id.progress;
if (isPrimary && animate) {
final ObjectAnimator animator = ObjectAnimator.ofFloat(this, VISUAL_PROGRESS, scale);
animator.setAutoCancel(true);
animator.setDuration(PROGRESS_ANIM_DURATION);
animator.setInterpolator(PROGRESS_ANIM_INTERPOLATOR);
animator.start();
} else {
setVisualProgress(id, scale);
}
if (isPrimary && callBackToApp) {
onProgressRefresh(scale, fromUser, progress);
}
}
void onProgressRefresh(float scale, boolean fromUser, int progress) {
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
scheduleAccessibilityEventSender();
}
}
/**
* Sets the visual state of a progress indicator.
*
* @param id the identifier of the progress indicator
* @param progress the visual progress in the range [0...1]
*/
private void setVisualProgress(int id, float progress) {
mVisualProgress = progress;
Drawable d = mCurrentDrawable;
if (d instanceof LayerDrawable) {
d = ((LayerDrawable) d).findDrawableByLayerId(id);
if (d == null) {
// If we can't find the requested layer, fall back to setting
// the level of the entire drawable. This will break if
// progress is set on multiple elements, but the theme-default
// drawable will always have all layer IDs present.
d = mCurrentDrawable;
}
}
if (d != null) {
final int level = (int) (progress * MAX_LEVEL);
d.setLevel(level);
} else {
invalidate();
}
onVisualProgressChanged(id, progress);
}
/**
* Called when the visual state of a progress indicator changes.
*
* @param id the identifier of the progress indicator
* @param progress the visual progress in the range [0...1]
*/
void onVisualProgressChanged(int id, float progress) {
// Stub method.
}
private synchronized void refreshProgress(int id, int progress, boolean fromUser,
boolean animate) {
if (mUiThreadId == Thread.currentThread().getId()) {
doRefreshProgress(id, progress, fromUser, true, animate);
} else {
if (mRefreshProgressRunnable == null) {
mRefreshProgressRunnable = new RefreshProgressRunnable();
}
final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate);
mRefreshData.add(rd);
if (mAttached && !mRefreshIsPosted) {
post(mRefreshProgressRunnable);
mRefreshIsPosted = true;
}
}
}
/**
* Sets the current progress to the specified value. Does not do anything
* if the progress bar is in indeterminate mode.
* <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 0 and {@link #getMax()}
*
* @see #setIndeterminate(boolean)
* @see #isIndeterminate()
* @see #getProgress()
* @see #incrementProgressBy(int)
*/
@android.view.RemotableViewMethod
public synchronized void setProgress(int progress) {
setProgressInternal(progress, false, false);
}
/**
* Sets the current progress to the specified value, optionally animating
* the visual position between the current and target values.
* <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 0 and {@link #getMax()}
* @param animate {@code true} to animate between the current and target
* values or {@code false} to not animate
*/
public void setProgress(int progress, boolean animate) {
setProgressInternal(progress, false, animate);
}
@android.view.RemotableViewMethod
synchronized boolean setProgressInternal(int progress, boolean fromUser, boolean animate) {
if (mIndeterminate) {
// Not applicable.
return false;
}
progress = MathUtils.constrain(progress, 0, mMax);
if (progress == mProgress) {
// No change from current.
return false;
}
mProgress = progress;
refreshProgress(R.id.progress, mProgress, fromUser, animate);
return true;
}
/**
* <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 0 and {@link #getMax()}
* @see #setIndeterminate(boolean)
* @see #isIndeterminate()
* @see #getSecondaryProgress()
* @see #incrementSecondaryProgressBy(int)
*/
@android.view.RemotableViewMethod
public synchronized void setSecondaryProgress(int secondaryProgress) {
if (mIndeterminate) {
return;
}
if (secondaryProgress < 0) {
secondaryProgress = 0;
}
if (secondaryProgress > mMax) {
secondaryProgress = mMax;
}
if (secondaryProgress != mSecondaryProgress) {
mSecondaryProgress = secondaryProgress;
refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false);
}
}
/**
* <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 0 and {@link #getMax()}
*
* @see #setIndeterminate(boolean)
* @see #isIndeterminate()
* @see #setProgress(int)
* @see #setMax(int)
* @see #getMax()
*/
@ViewDebug.ExportedProperty(category = "progress")
public synchronized int getProgress() {
return mIndeterminate ? 0 : mProgress;
}
/**
* <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 0 and {@link #getMax()}
*
* @see #setIndeterminate(boolean)
* @see #isIndeterminate()
* @see #setSecondaryProgress(int)
* @see #setMax(int)
* @see #getMax()
*/
@ViewDebug.ExportedProperty(category = "progress")
public synchronized int getSecondaryProgress() {
return mIndeterminate ? 0 : mSecondaryProgress;
}
/**
* <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")
public synchronized int getMax() {
return mMax;
}
/**
* <p>Set the range of the progress bar to 0...<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 (max < 0) {
max = 0;
}
if (max != mMax) {
mMax = max;
postInvalidate();
if (mProgress > max) {
mProgress = max;
}
refreshProgress(R.id.progress, mProgress, false, false);
}
}
/**
* <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>
*/
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>
*/
void stopAnimation() {
mHasAnimation = false;
if (mIndeterminateDrawable instanceof Animatable) {
((Animatable) mIndeterminateDrawable).stop();
mShouldStartAnimationDrawable = false;
}
postInvalidate();
}
/**
* Sets the acceleration curve for the indeterminate animation.
* The interpolator is loaded as a resource from the specified context.
*
* @param context The application environment
* @param resID The resource identifier of the interpolator to load
*/
public void setInterpolator(Context context, @InterpolatorRes int resID) {
setInterpolator(AnimationUtils.loadInterpolator(context, resID));
}
/**
* Sets the acceleration curve for the indeterminate animation.
* Defaults to a linear interpolation.
*
* @param interpolator The interpolator which defines the acceleration curve
*/
public void setInterpolator(Interpolator interpolator) {
mInterpolator = interpolator;
}
/**
* Gets the acceleration curve type for the indeterminate animation.
*
* @return the {@link Interpolator} associated to this animation
*/
public Interpolator getInterpolator() {
return mInterpolator;
}
@Override
public void onVisibilityAggregated(boolean isVisible) {
super.onVisibilityAggregated(isVisible);
if (isVisible != mAggregatedIsVisible) {
mAggregatedIsVisible = isVisible;
if (mIndeterminate) {
// let's be nice with the UI thread
if (isVisible) {
startAnimation();
} else {
stopAnimation();
}
}
if (mCurrentDrawable != null) {
mCurrentDrawable.setVisible(isVisible, false);
}
}
}
@Override
public void invalidateDrawable(@NonNull Drawable dr) {
if (!mInDrawing) {
if (verifyDrawable(dr)) {
final Rect dirty = dr.getBounds();
final int scrollX = mScrollX + mPaddingLeft;
final int scrollY = mScrollY + mPaddingTop;
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
} else {
super.invalidateDrawable(dr);
}
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
updateDrawableBounds(w, h);
}
private void updateDrawableBounds(int w, int h) {
// onDraw will translate the canvas so we draw starting at 0,0.
// Subtract out padding for the purposes of the calculations below.
w -= mPaddingRight + mPaddingLeft;
h -= mPaddingTop + mPaddingBottom;
int right = w;
int bottom = h;
int top = 0;
int left = 0;
if (mIndeterminateDrawable != null) {
// Aspect ratio logic does not apply to AnimationDrawables
if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
// Maintain aspect ratio. Certain kinds of animated drawables
// get very confused otherwise.
final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
final float boundAspect = (float) w / h;
if (intrinsicAspect != boundAspect) {
if (boundAspect > intrinsicAspect) {
// New width is larger. Make it smaller to match height.
final int width = (int) (h * intrinsicAspect);
left = (w - width) / 2;
right = left + width;
} else {
// New height is larger. Make it smaller to match width.
final int height = (int) (w * (1 / intrinsicAspect));
top = (h - height) / 2;
bottom = top + height;
}
}
}
if (isLayoutRtl() && mMirrorForRtl) {
int tempLeft = left;
left = w - right;
right = w - tempLeft;
}
mIndeterminateDrawable.setBounds(left, top, right, bottom);
}
if (mProgressDrawable != null) {
mProgressDrawable.setBounds(0, 0, right, bottom);
}
}
@Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawTrack(canvas);
}
/**
* Draws the progress bar track.
*/
void drawTrack(Canvas canvas) {
final Drawable d = mCurrentDrawable;
if (d != null) {
// Translate canvas so a indeterminate circular progress bar with padding
// rotates properly in its animation
final int saveCount = canvas.save();
if (isLayoutRtl() && mMirrorForRtl) {
canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
canvas.scale(-1.0f, 1.0f);
} else {
canvas.translate(mPaddingLeft, mPaddingTop);
}
final long time = getDrawingTime();
if (mHasAnimation) {
mAnimation.getTransformation(time, mTransformation);
final float scale = mTransformation.getAlpha();
try {
mInDrawing = true;
d.setLevel((int) (scale * MAX_LEVEL));
} finally {
mInDrawing = false;
}
postInvalidateOnAnimation();
}
d.draw(canvas);
canvas.restoreToCount(saveCount);
if (mShouldStartAnimationDrawable && d instanceof Animatable) {
((Animatable) d).start();
mShouldStartAnimationDrawable = false;
}
}
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int dw = 0;
int dh = 0;
final Drawable d = mCurrentDrawable;
if (d != null) {
dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
}
updateDrawableState();
dw += mPaddingLeft + mPaddingRight;
dh += mPaddingTop + mPaddingBottom;
final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0);
final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0);
setMeasuredDimension(measuredWidth, measuredHeight);
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
updateDrawableState();
}
private void updateDrawableState() {
final int[] state = getDrawableState();
boolean changed = false;
final Drawable progressDrawable = mProgressDrawable;
if (progressDrawable != null && progressDrawable.isStateful()) {
changed |= progressDrawable.setState(state);
}
final Drawable indeterminateDrawable = mIndeterminateDrawable;
if (indeterminateDrawable != null && indeterminateDrawable.isStateful()) {
changed |= indeterminateDrawable.setState(state);
}
if (changed) {
invalidate();
}
}
@Override
public void drawableHotspotChanged(float x, float y) {
super.drawableHotspotChanged(x, y);
if (mProgressDrawable != null) {
mProgressDrawable.setHotspot(x, y);
}
if (mIndeterminateDrawable != null) {
mIndeterminateDrawable.setHotspot(x, y);
}
}
static class SavedState extends BaseSavedState {
int progress;
int secondaryProgress;
/**
* Constructor called from {@link ProgressBar#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in) {
super(in);
progress = in.readInt();
secondaryProgress = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(progress);
out.writeInt(secondaryProgress);
}
public static final Parcelable.Creator<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;
}
if (mAccessibilityEventSender != null) {
removeCallbacks(mAccessibilityEventSender);
}
// This should come after stopAnimation(), otherwise an invalidate message remains in the
// queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
super.onDetachedFromWindow();
mAttached = false;
}
@Override
public CharSequence getAccessibilityClassName() {
return ProgressBar.class.getName();
}
/** @hide */
@Override
public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
super.onInitializeAccessibilityEventInternal(event);
event.setItemCount(mMax);
event.setCurrentItemIndex(mProgress);
}
/** @hide */
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
if (!isIndeterminate()) {
AccessibilityNodeInfo.RangeInfo rangeInfo = AccessibilityNodeInfo.RangeInfo.obtain(
AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT, 0, getMax(), getProgress());
info.setRangeInfo(rangeInfo);
}
}
/**
* Schedule a command for sending an accessibility event.
* </br>
* Note: A command is used to ensure that accessibility events
* are sent at most one in a given time frame to save
* system resources while the progress changes quickly.
*/
private void scheduleAccessibilityEventSender() {
if (mAccessibilityEventSender == null) {
mAccessibilityEventSender = new AccessibilityEventSender();
} else {
removeCallbacks(mAccessibilityEventSender);
}
postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
}
/** @hide */
@Override
protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
super.encodeProperties(stream);
stream.addProperty("progress:max", getMax());
stream.addProperty("progress:progress", getProgress());
stream.addProperty("progress:secondaryProgress", getSecondaryProgress());
stream.addProperty("progress:indeterminate", isIndeterminate());
}
/**
* Command for sending an accessibility event.
*/
private class AccessibilityEventSender implements Runnable {
public void run() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
}
private static class ProgressTintInfo {
ColorStateList mIndeterminateTintList;
PorterDuff.Mode mIndeterminateTintMode;
boolean mHasIndeterminateTint;
boolean mHasIndeterminateTintMode;
ColorStateList mProgressTintList;
PorterDuff.Mode mProgressTintMode;
boolean mHasProgressTint;
boolean mHasProgressTintMode;
ColorStateList mProgressBackgroundTintList;
PorterDuff.Mode mProgressBackgroundTintMode;
boolean mHasProgressBackgroundTint;
boolean mHasProgressBackgroundTintMode;
ColorStateList mSecondaryProgressTintList;
PorterDuff.Mode mSecondaryProgressTintMode;
boolean mHasSecondaryProgressTint;
boolean mHasSecondaryProgressTintMode;
}
/**
* Property wrapper around the visual state of the {@code progress} functionality
* handled by the {@link ProgressBar#setProgress(int, boolean)} method. This does
* not correspond directly to the actual progress -- only the visual state.
*/
private final FloatProperty<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;
}
};
}