2 * Copyright (C) 2006 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.actionbarsherlock.internal.widget;
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Bitmap;
22 import android.graphics.BitmapShader;
23 import android.graphics.Canvas;
24 import android.graphics.Rect;
25 import android.graphics.Shader;
26 import android.graphics.drawable.Animatable;
27 import android.graphics.drawable.AnimationDrawable;
28 import android.graphics.drawable.BitmapDrawable;
29 import android.graphics.drawable.ClipDrawable;
30 import android.graphics.drawable.Drawable;
31 import android.graphics.drawable.LayerDrawable;
32 import android.graphics.drawable.ShapeDrawable;
33 import android.graphics.drawable.shapes.RoundRectShape;
34 import android.graphics.drawable.shapes.Shape;
35 import android.os.Build;
36 import android.os.Parcel;
37 import android.os.Parcelable;
38 import android.os.SystemClock;
39 import android.util.AttributeSet;
40 import android.view.Gravity;
41 import android.view.View;
42 import android.view.ViewDebug;
43 import android.view.accessibility.AccessibilityEvent;
44 import android.view.accessibility.AccessibilityManager;
45 import android.view.animation.AlphaAnimation;
46 import android.view.animation.Animation;
47 import android.view.animation.AnimationUtils;
48 import android.view.animation.Interpolator;
49 import android.view.animation.LinearInterpolator;
50 import android.view.animation.Transformation;
51 import android.widget.RemoteViews.RemoteView;
56 * Visual indicator of progress in some operation. Displays a bar to the user
57 * representing how far the operation has progressed; the application can
58 * change the amount of progress (modifying the length of the bar) as it moves
59 * forward. There is also a secondary progress displayable on a progress bar
60 * which is useful for displaying intermediate progress, such as the buffer
61 * level during a streaming playback progress bar.
65 * A progress bar can also be made indeterminate. In indeterminate mode, the
66 * progress bar shows a cyclic animation without an indication of progress. This mode is used by
67 * applications when the length of the task is unknown. The indeterminate progress bar can be either
68 * a spinning wheel or a horizontal bar.
71 * <p>The following code example shows how a progress bar can be used from
72 * a worker thread to update the user interface to notify the user of progress:
76 * public class MyActivity extends Activity {
77 * private static final int PROGRESS = 0x1;
79 * private ProgressBar mProgress;
80 * private int mProgressStatus = 0;
82 * private Handler mHandler = new Handler();
84 * protected void onCreate(Bundle icicle) {
85 * super.onCreate(icicle);
87 * setContentView(R.layout.progressbar_activity);
89 * mProgress = (ProgressBar) findViewById(R.id.progress_bar);
91 * // Start lengthy operation in a background thread
92 * new Thread(new Runnable() {
94 * while (mProgressStatus < 100) {
95 * mProgressStatus = doWork();
97 * // Update the progress bar
98 * mHandler.post(new Runnable() {
100 * mProgress.setProgress(mProgressStatus);
109 * <p>To add a progress bar to a layout file, you can use the {@code <ProgressBar>} element.
110 * By default, the progress bar is a spinning wheel (an indeterminate indicator). To change to a
111 * horizontal progress bar, apply the {@link android.R.style#Widget_ProgressBar_Horizontal
112 * Widget.ProgressBar.Horizontal} style, like so:</p>
116 * style="@android:style/Widget.ProgressBar.Horizontal"
119 * <p>If you will use the progress bar to show real progress, you must use the horizontal bar. You
120 * can then increment the progress with {@link #incrementProgressBy incrementProgressBy()} or
121 * {@link #setProgress setProgress()}. By default, the progress bar is full when it reaches 100. If
122 * necessary, you can adjust the maximum value (the value for a full bar) using the {@link
123 * android.R.styleable#ProgressBar_max android:max} attribute. Other attributes available are listed
126 * <p>Another common style to apply to the progress bar is {@link
127 * android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}, which shows a smaller
128 * version of the spinning wheel—useful when waiting for content to load.
129 * For example, you can insert this kind of progress bar into your default layout for
130 * a view that will be populated by some content fetched from the Internet—the spinning wheel
131 * appears immediately and when your application receives the content, it replaces the progress bar
132 * with the loaded content. For example:</p>
136 * android:orientation="horizontal"
139 * android:layout_width="wrap_content"
140 * android:layout_height="wrap_content"
141 * style="@android:style/Widget.ProgressBar.Small"
142 * android:layout_marginRight="5dp" />
144 * android:layout_width="wrap_content"
145 * android:layout_height="wrap_content"
146 * android:text="@string/loading" />
147 * </LinearLayout></pre>
149 * <p>Other progress bar styles provided by the system include:</p>
151 * <li>{@link android.R.style#Widget_ProgressBar_Horizontal Widget.ProgressBar.Horizontal}</li>
152 * <li>{@link android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}</li>
153 * <li>{@link android.R.style#Widget_ProgressBar_Large Widget.ProgressBar.Large}</li>
154 * <li>{@link android.R.style#Widget_ProgressBar_Inverse Widget.ProgressBar.Inverse}</li>
155 * <li>{@link android.R.style#Widget_ProgressBar_Small_Inverse
156 * Widget.ProgressBar.Small.Inverse}</li>
157 * <li>{@link android.R.style#Widget_ProgressBar_Large_Inverse
158 * Widget.ProgressBar.Large.Inverse}</li>
160 * <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary
161 * if your application uses a light colored theme (a white background).</p>
163 * <p><strong>XML attributes</b></strong>
165 * See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
166 * {@link android.R.styleable#View View Attributes}
169 * @attr ref android.R.styleable#ProgressBar_animationResolution
170 * @attr ref android.R.styleable#ProgressBar_indeterminate
171 * @attr ref android.R.styleable#ProgressBar_indeterminateBehavior
172 * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable
173 * @attr ref android.R.styleable#ProgressBar_indeterminateDuration
174 * @attr ref android.R.styleable#ProgressBar_indeterminateOnly
175 * @attr ref android.R.styleable#ProgressBar_interpolator
176 * @attr ref android.R.styleable#ProgressBar_max
177 * @attr ref android.R.styleable#ProgressBar_maxHeight
178 * @attr ref android.R.styleable#ProgressBar_maxWidth
179 * @attr ref android.R.styleable#ProgressBar_minHeight
180 * @attr ref android.R.styleable#ProgressBar_minWidth
181 * @attr ref android.R.styleable#ProgressBar_progress
182 * @attr ref android.R.styleable#ProgressBar_progressDrawable
183 * @attr ref android.R.styleable#ProgressBar_secondaryProgress
186 public class IcsProgressBar extends View {
187 private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
188 private static final int MAX_LEVEL = 10000;
189 private static final int ANIMATION_RESOLUTION = 200;
190 private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200;
192 private static final int[] ProgressBar = new int[] {
193 android.R.attr.maxWidth,
194 android.R.attr.maxHeight,
196 android.R.attr.progress,
197 android.R.attr.secondaryProgress,
198 android.R.attr.indeterminate,
199 android.R.attr.indeterminateOnly,
200 android.R.attr.indeterminateDrawable,
201 android.R.attr.progressDrawable,
202 android.R.attr.indeterminateDuration,
203 android.R.attr.indeterminateBehavior,
204 android.R.attr.minWidth,
205 android.R.attr.minHeight,
206 android.R.attr.interpolator,
207 android.R.attr.animationResolution,
209 private static final int ProgressBar_maxWidth = 0;
210 private static final int ProgressBar_maxHeight = 1;
211 private static final int ProgressBar_max = 2;
212 private static final int ProgressBar_progress = 3;
213 private static final int ProgressBar_secondaryProgress = 4;
214 private static final int ProgressBar_indeterminate = 5;
215 private static final int ProgressBar_indeterminateOnly = 6;
216 private static final int ProgressBar_indeterminateDrawable = 7;
217 private static final int ProgressBar_progressDrawable = 8;
218 private static final int ProgressBar_indeterminateDuration = 9;
219 private static final int ProgressBar_indeterminateBehavior = 10;
220 private static final int ProgressBar_minWidth = 11;
221 private static final int ProgressBar_minHeight = 12;
222 private static final int ProgressBar_interpolator = 13;
223 private static final int ProgressBar_animationResolution = 14;
230 private int mProgress;
231 private int mSecondaryProgress;
234 private int mBehavior;
235 private int mDuration;
236 private boolean mIndeterminate;
237 private boolean mOnlyIndeterminate;
238 private Transformation mTransformation;
239 private AlphaAnimation mAnimation;
240 private Drawable mIndeterminateDrawable;
241 private int mIndeterminateRealLeft;
242 private int mIndeterminateRealTop;
243 private Drawable mProgressDrawable;
244 private Drawable mCurrentDrawable;
246 private boolean mNoInvalidate;
247 private Interpolator mInterpolator;
248 private RefreshProgressRunnable mRefreshProgressRunnable;
249 private long mUiThreadId;
250 private boolean mShouldStartAnimationDrawable;
251 private long mLastDrawTime;
253 private boolean mInDrawing;
255 private int mAnimationResolution;
257 private AccessibilityManager mAccessibilityManager;
258 private AccessibilityEventSender mAccessibilityEventSender;
261 * Create a new progress bar with range 0...100 and initial progress of 0.
262 * @param context the application environment
264 public IcsProgressBar(Context context) {
268 public IcsProgressBar(Context context, AttributeSet attrs) {
269 this(context, attrs, android.R.attr.progressBarStyle);
272 public IcsProgressBar(Context context, AttributeSet attrs, int defStyle) {
273 this(context, attrs, defStyle, 0);
279 public IcsProgressBar(Context context, AttributeSet attrs, int defStyle, int styleRes) {
280 super(context, attrs, defStyle);
281 mUiThreadId = Thread.currentThread().getId();
285 context.obtainStyledAttributes(attrs, /*R.styleable.*/ProgressBar, defStyle, styleRes);
287 mNoInvalidate = true;
289 Drawable drawable = a.getDrawable(/*R.styleable.*/ProgressBar_progressDrawable);
290 if (drawable != null) {
291 drawable = tileify(drawable, false);
292 // Calling this method can set mMaxHeight, make sure the corresponding
293 // XML attribute for mMaxHeight is read after calling this method
294 setProgressDrawable(drawable);
298 mDuration = a.getInt(/*R.styleable.*/ProgressBar_indeterminateDuration, mDuration);
300 mMinWidth = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_minWidth, mMinWidth);
301 mMaxWidth = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_maxWidth, mMaxWidth);
302 mMinHeight = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_minHeight, mMinHeight);
303 mMaxHeight = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_maxHeight, mMaxHeight);
305 mBehavior = a.getInt(/*R.styleable.*/ProgressBar_indeterminateBehavior, mBehavior);
307 final int resID = a.getResourceId(
308 /*com.android.internal.R.styleable.*/ProgressBar_interpolator,
309 android.R.anim.linear_interpolator); // default to linear interpolator
311 setInterpolator(context, resID);
314 setMax(a.getInt(/*R.styleable.*/ProgressBar_max, mMax));
316 setProgress(a.getInt(/*R.styleable.*/ProgressBar_progress, mProgress));
318 setSecondaryProgress(
319 a.getInt(/*R.styleable.*/ProgressBar_secondaryProgress, mSecondaryProgress));
321 drawable = a.getDrawable(/*R.styleable.*/ProgressBar_indeterminateDrawable);
322 if (drawable != null) {
323 drawable = tileifyIndeterminate(drawable);
324 setIndeterminateDrawable(drawable);
327 mOnlyIndeterminate = a.getBoolean(
328 /*R.styleable.*/ProgressBar_indeterminateOnly, mOnlyIndeterminate);
330 mNoInvalidate = false;
332 setIndeterminate(mOnlyIndeterminate || a.getBoolean(
333 /*R.styleable.*/ProgressBar_indeterminate, mIndeterminate));
335 mAnimationResolution = a.getInteger(/*R.styleable.*/ProgressBar_animationResolution,
336 ANIMATION_RESOLUTION);
340 mAccessibilityManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE);
344 * Converts a drawable to a tiled version of itself. It will recursively
345 * traverse layer and state list drawables.
347 private Drawable tileify(Drawable drawable, boolean clip) {
349 if (drawable instanceof LayerDrawable) {
350 LayerDrawable background = (LayerDrawable) drawable;
351 final int N = background.getNumberOfLayers();
352 Drawable[] outDrawables = new Drawable[N];
354 for (int i = 0; i < N; i++) {
355 int id = background.getId(i);
356 outDrawables[i] = tileify(background.getDrawable(i),
357 (id == android.R.id.progress || id == android.R.id.secondaryProgress));
360 LayerDrawable newBg = new LayerDrawable(outDrawables);
362 for (int i = 0; i < N; i++) {
363 newBg.setId(i, background.getId(i));
368 }/* else if (drawable instanceof StateListDrawable) {
369 StateListDrawable in = (StateListDrawable) drawable;
370 StateListDrawable out = new StateListDrawable();
371 int numStates = in.getStateCount();
372 for (int i = 0; i < numStates; i++) {
373 out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
377 }*/ else if (drawable instanceof BitmapDrawable) {
378 final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
379 if (mSampleTile == null) {
380 mSampleTile = tileBitmap;
383 final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
385 final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
386 Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
387 shapeDrawable.getPaint().setShader(bitmapShader);
389 return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
390 ClipDrawable.HORIZONTAL) : shapeDrawable;
396 Shape getDrawableShape() {
397 final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
398 return new RoundRectShape(roundedCorners, null, null);
402 * Convert a AnimationDrawable for use as a barberpole animation.
403 * Each frame of the animation is wrapped in a ClipDrawable and
404 * given a tiling BitmapShader.
406 private Drawable tileifyIndeterminate(Drawable drawable) {
407 if (drawable instanceof AnimationDrawable) {
408 AnimationDrawable background = (AnimationDrawable) drawable;
409 final int N = background.getNumberOfFrames();
410 AnimationDrawable newBg = new AnimationDrawable();
411 newBg.setOneShot(background.isOneShot());
413 for (int i = 0; i < N; i++) {
414 Drawable frame = tileify(background.getFrame(i), true);
415 frame.setLevel(10000);
416 newBg.addFrame(frame, background.getDuration(i));
418 newBg.setLevel(10000);
426 * Initialize the progress bar's default values:
429 * <li>progress = 0</li>
431 * <li>animation duration = 4000 ms</li>
432 * <li>indeterminate = false</li>
433 * <li>behavior = repeat</li>
436 private void initProgressBar() {
439 mSecondaryProgress = 0;
440 mIndeterminate = false;
441 mOnlyIndeterminate = false;
443 mBehavior = AlphaAnimation.RESTART;
451 * <p>Indicate whether this progress bar is in indeterminate mode.</p>
453 * @return true if the progress bar is in indeterminate mode
455 @ViewDebug.ExportedProperty(category = "progress")
456 public synchronized boolean isIndeterminate() {
457 return mIndeterminate;
461 * <p>Change the indeterminate mode for this progress bar. In indeterminate
462 * mode, the progress is ignored and the progress bar shows an infinite
463 * animation instead.</p>
465 * If this progress bar's style only supports indeterminate mode (such as the circular
466 * progress bars), then this will be ignored.
468 * @param indeterminate true to enable the indeterminate mode
470 public synchronized void setIndeterminate(boolean indeterminate) {
471 if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
472 mIndeterminate = indeterminate;
475 // swap between indeterminate and regular backgrounds
476 mCurrentDrawable = mIndeterminateDrawable;
479 mCurrentDrawable = mProgressDrawable;
486 * <p>Get the drawable used to draw the progress bar in
487 * indeterminate mode.</p>
489 * @return a {@link android.graphics.drawable.Drawable} instance
491 * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
492 * @see #setIndeterminate(boolean)
494 public Drawable getIndeterminateDrawable() {
495 return mIndeterminateDrawable;
499 * <p>Define the drawable used to draw the progress bar in
500 * indeterminate mode.</p>
502 * @param d the new drawable
504 * @see #getIndeterminateDrawable()
505 * @see #setIndeterminate(boolean)
507 public void setIndeterminateDrawable(Drawable d) {
511 mIndeterminateDrawable = d;
512 if (mIndeterminate) {
513 mCurrentDrawable = d;
519 * <p>Get the drawable used to draw the progress bar in
522 * @return a {@link android.graphics.drawable.Drawable} instance
524 * @see #setProgressDrawable(android.graphics.drawable.Drawable)
525 * @see #setIndeterminate(boolean)
527 public Drawable getProgressDrawable() {
528 return mProgressDrawable;
532 * <p>Define the drawable used to draw the progress bar in
535 * @param d the new drawable
537 * @see #getProgressDrawable()
538 * @see #setIndeterminate(boolean)
540 public void setProgressDrawable(Drawable d) {
542 if (mProgressDrawable != null && d != mProgressDrawable) {
543 mProgressDrawable.setCallback(null);
552 // Make sure the ProgressBar is always tall enough
553 int drawableHeight = d.getMinimumHeight();
554 if (mMaxHeight < drawableHeight) {
555 mMaxHeight = drawableHeight;
559 mProgressDrawable = d;
560 if (!mIndeterminate) {
561 mCurrentDrawable = d;
566 updateDrawableBounds(getWidth(), getHeight());
567 updateDrawableState();
568 doRefreshProgress(android.R.id.progress, mProgress, false, false);
569 doRefreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false, false);
574 * @return The drawable currently used to draw the progress bar
576 Drawable getCurrentDrawable() {
577 return mCurrentDrawable;
581 protected boolean verifyDrawable(Drawable who) {
582 return who == mProgressDrawable || who == mIndeterminateDrawable
583 || super.verifyDrawable(who);
587 public void jumpDrawablesToCurrentState() {
588 super.jumpDrawablesToCurrentState();
589 if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
590 if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
594 public void postInvalidate() {
595 if (!mNoInvalidate) {
596 super.postInvalidate();
600 private class RefreshProgressRunnable implements Runnable {
603 private int mProgress;
604 private boolean mFromUser;
606 RefreshProgressRunnable(int id, int progress, boolean fromUser) {
608 mProgress = progress;
609 mFromUser = fromUser;
613 doRefreshProgress(mId, mProgress, mFromUser, true);
614 // Put ourselves back in the cache when we are done
615 mRefreshProgressRunnable = this;
618 public void setup(int id, int progress, boolean fromUser) {
620 mProgress = progress;
621 mFromUser = fromUser;
626 private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
627 boolean callBackToApp) {
628 float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
629 final Drawable d = mCurrentDrawable;
631 Drawable progressDrawable = null;
633 if (d instanceof LayerDrawable) {
634 progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
637 final int level = (int) (scale * MAX_LEVEL);
638 (progressDrawable != null ? progressDrawable : d).setLevel(level);
643 if (callBackToApp && id == android.R.id.progress) {
644 onProgressRefresh(scale, fromUser);
648 void onProgressRefresh(float scale, boolean fromUser) {
649 if (mAccessibilityManager.isEnabled()) {
650 scheduleAccessibilityEventSender();
654 private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
655 if (mUiThreadId == Thread.currentThread().getId()) {
656 doRefreshProgress(id, progress, fromUser, true);
658 RefreshProgressRunnable r;
659 if (mRefreshProgressRunnable != null) {
660 // Use cached RefreshProgressRunnable if available
661 r = mRefreshProgressRunnable;
663 mRefreshProgressRunnable = null;
664 r.setup(id, progress, fromUser);
667 r = new RefreshProgressRunnable(id, progress, fromUser);
674 * <p>Set the current progress to the specified value. Does not do anything
675 * if the progress bar is in indeterminate mode.</p>
677 * @param progress the new progress, between 0 and {@link #getMax()}
679 * @see #setIndeterminate(boolean)
680 * @see #isIndeterminate()
681 * @see #getProgress()
682 * @see #incrementProgressBy(int)
684 public synchronized void setProgress(int progress) {
685 setProgress(progress, false);
688 synchronized void setProgress(int progress, boolean fromUser) {
689 if (mIndeterminate) {
697 if (progress > mMax) {
701 if (progress != mProgress) {
702 mProgress = progress;
703 refreshProgress(android.R.id.progress, mProgress, fromUser);
709 * Set the current secondary progress to the specified value. Does not do
710 * anything if the progress bar is in indeterminate mode.
713 * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
714 * @see #setIndeterminate(boolean)
715 * @see #isIndeterminate()
716 * @see #getSecondaryProgress()
717 * @see #incrementSecondaryProgressBy(int)
719 public synchronized void setSecondaryProgress(int secondaryProgress) {
720 if (mIndeterminate) {
724 if (secondaryProgress < 0) {
725 secondaryProgress = 0;
728 if (secondaryProgress > mMax) {
729 secondaryProgress = mMax;
732 if (secondaryProgress != mSecondaryProgress) {
733 mSecondaryProgress = secondaryProgress;
734 refreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false);
739 * <p>Get the progress bar's current level of progress. Return 0 when the
740 * progress bar is in indeterminate mode.</p>
742 * @return the current progress, between 0 and {@link #getMax()}
744 * @see #setIndeterminate(boolean)
745 * @see #isIndeterminate()
746 * @see #setProgress(int)
750 @ViewDebug.ExportedProperty(category = "progress")
751 public synchronized int getProgress() {
752 return mIndeterminate ? 0 : mProgress;
756 * <p>Get the progress bar's current level of secondary progress. Return 0 when the
757 * progress bar is in indeterminate mode.</p>
759 * @return the current secondary progress, between 0 and {@link #getMax()}
761 * @see #setIndeterminate(boolean)
762 * @see #isIndeterminate()
763 * @see #setSecondaryProgress(int)
767 @ViewDebug.ExportedProperty(category = "progress")
768 public synchronized int getSecondaryProgress() {
769 return mIndeterminate ? 0 : mSecondaryProgress;
773 * <p>Return the upper limit of this progress bar's range.</p>
775 * @return a positive integer
778 * @see #getProgress()
779 * @see #getSecondaryProgress()
781 @ViewDebug.ExportedProperty(category = "progress")
782 public synchronized int getMax() {
787 * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
789 * @param max the upper range of this progress bar
792 * @see #setProgress(int)
793 * @see #setSecondaryProgress(int)
795 public synchronized void setMax(int max) {
803 if (mProgress > max) {
806 refreshProgress(android.R.id.progress, mProgress, false);
811 * <p>Increase the progress bar's progress by the specified amount.</p>
813 * @param diff the amount by which the progress must be increased
815 * @see #setProgress(int)
817 public synchronized final void incrementProgressBy(int diff) {
818 setProgress(mProgress + diff);
822 * <p>Increase the progress bar's secondary progress by the specified amount.</p>
824 * @param diff the amount by which the secondary progress must be increased
826 * @see #setSecondaryProgress(int)
828 public synchronized final void incrementSecondaryProgressBy(int diff) {
829 setSecondaryProgress(mSecondaryProgress + diff);
833 * <p>Start the indeterminate progress animation.</p>
835 void startAnimation() {
836 if (getVisibility() != VISIBLE) {
840 if (mIndeterminateDrawable instanceof Animatable) {
841 mShouldStartAnimationDrawable = true;
844 if (mInterpolator == null) {
845 mInterpolator = new LinearInterpolator();
848 mTransformation = new Transformation();
849 mAnimation = new AlphaAnimation(0.0f, 1.0f);
850 mAnimation.setRepeatMode(mBehavior);
851 mAnimation.setRepeatCount(Animation.INFINITE);
852 mAnimation.setDuration(mDuration);
853 mAnimation.setInterpolator(mInterpolator);
854 mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
860 * <p>Stop the indeterminate progress animation.</p>
862 void stopAnimation() {
864 mTransformation = null;
865 if (mIndeterminateDrawable instanceof Animatable) {
866 ((Animatable) mIndeterminateDrawable).stop();
867 mShouldStartAnimationDrawable = false;
873 * Sets the acceleration curve for the indeterminate animation.
874 * The interpolator is loaded as a resource from the specified context.
876 * @param context The application environment
877 * @param resID The resource identifier of the interpolator to load
879 public void setInterpolator(Context context, int resID) {
880 setInterpolator(AnimationUtils.loadInterpolator(context, resID));
884 * Sets the acceleration curve for the indeterminate animation.
885 * Defaults to a linear interpolation.
887 * @param interpolator The interpolator which defines the acceleration curve
889 public void setInterpolator(Interpolator interpolator) {
890 mInterpolator = interpolator;
894 * Gets the acceleration curve type for the indeterminate animation.
896 * @return the {@link Interpolator} associated to this animation
898 public Interpolator getInterpolator() {
899 return mInterpolator;
903 public void setVisibility(int v) {
904 if (getVisibility() != v) {
905 super.setVisibility(v);
907 if (mIndeterminate) {
908 // let's be nice with the UI thread
909 if (v == GONE || v == INVISIBLE) {
919 protected void onVisibilityChanged(View changedView, int visibility) {
920 super.onVisibilityChanged(changedView, visibility);
922 if (mIndeterminate) {
923 // let's be nice with the UI thread
924 if (visibility == GONE || visibility == INVISIBLE) {
933 public void invalidateDrawable(Drawable dr) {
935 if (verifyDrawable(dr)) {
936 final Rect dirty = dr.getBounds();
937 final int scrollX = getScrollX() + getPaddingLeft();
938 final int scrollY = getScrollY() + getPaddingTop();
940 invalidate(dirty.left + scrollX, dirty.top + scrollY,
941 dirty.right + scrollX, dirty.bottom + scrollY);
943 super.invalidateDrawable(dr);
952 public int getResolvedLayoutDirection(Drawable who) {
953 return (who == mProgressDrawable || who == mIndeterminateDrawable) ?
954 getResolvedLayoutDirection() : super.getResolvedLayoutDirection(who);
959 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
960 updateDrawableBounds(w, h);
963 private void updateDrawableBounds(int w, int h) {
964 // onDraw will translate the canvas so we draw starting at 0,0
965 int right = w - getPaddingRight() - getPaddingLeft();
966 int bottom = h - getPaddingBottom() - getPaddingTop();
970 if (mIndeterminateDrawable != null) {
971 // Aspect ratio logic does not apply to AnimationDrawables
972 if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
973 // Maintain aspect ratio. Certain kinds of animated drawables
974 // get very confused otherwise.
975 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
976 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
977 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
978 final float boundAspect = (float) w / h;
979 if (intrinsicAspect != boundAspect) {
980 if (boundAspect > intrinsicAspect) {
981 // New width is larger. Make it smaller to match height.
982 final int width = (int) (h * intrinsicAspect);
983 left = (w - width) / 2;
984 right = left + width;
986 // New height is larger. Make it smaller to match width.
987 final int height = (int) (w * (1 / intrinsicAspect));
988 top = (h - height) / 2;
989 bottom = top + height;
993 mIndeterminateDrawable.setBounds(0, 0, right - left, bottom - top);
994 mIndeterminateRealLeft = left;
995 mIndeterminateRealTop = top;
998 if (mProgressDrawable != null) {
999 mProgressDrawable.setBounds(0, 0, right, bottom);
1004 protected synchronized void onDraw(Canvas canvas) {
1005 super.onDraw(canvas);
1007 Drawable d = mCurrentDrawable;
1009 // Translate canvas so a indeterminate circular progress bar with padding
1010 // rotates properly in its animation
1012 canvas.translate(getPaddingLeft() + mIndeterminateRealLeft, getPaddingTop() + mIndeterminateRealTop);
1013 long time = getDrawingTime();
1014 if (mAnimation != null) {
1015 mAnimation.getTransformation(time, mTransformation);
1016 float scale = mTransformation.getAlpha();
1019 d.setLevel((int) (scale * MAX_LEVEL));
1023 if (SystemClock.uptimeMillis() - mLastDrawTime >= mAnimationResolution) {
1024 mLastDrawTime = SystemClock.uptimeMillis();
1025 postInvalidateDelayed(mAnimationResolution);
1030 if (mShouldStartAnimationDrawable && d instanceof Animatable) {
1031 ((Animatable) d).start();
1032 mShouldStartAnimationDrawable = false;
1038 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1039 Drawable d = mCurrentDrawable;
1044 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
1045 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
1047 updateDrawableState();
1048 dw += getPaddingLeft() + getPaddingRight();
1049 dh += getPaddingTop() + getPaddingBottom();
1052 setMeasuredDimension(View.resolveSizeAndState(dw, widthMeasureSpec, 0),
1053 View.resolveSizeAndState(dh, heightMeasureSpec, 0));
1055 setMeasuredDimension(View.resolveSize(dw, widthMeasureSpec),
1056 View.resolveSize(dh, heightMeasureSpec));
1061 protected void drawableStateChanged() {
1062 super.drawableStateChanged();
1063 updateDrawableState();
1066 private void updateDrawableState() {
1067 int[] state = getDrawableState();
1069 if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
1070 mProgressDrawable.setState(state);
1073 if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
1074 mIndeterminateDrawable.setState(state);
1078 static class SavedState extends BaseSavedState {
1080 int secondaryProgress;
1083 * Constructor called from {@link IcsProgressBar#onSaveInstanceState()}
1085 SavedState(Parcelable superState) {
1090 * Constructor called from {@link #CREATOR}
1092 private SavedState(Parcel in) {
1094 progress = in.readInt();
1095 secondaryProgress = in.readInt();
1099 public void writeToParcel(Parcel out, int flags) {
1100 super.writeToParcel(out, flags);
1101 out.writeInt(progress);
1102 out.writeInt(secondaryProgress);
1105 public static final Parcelable.Creator<SavedState> CREATOR
1106 = new Parcelable.Creator<SavedState>() {
1107 public SavedState createFromParcel(Parcel in) {
1108 return new SavedState(in);
1111 public SavedState[] newArray(int size) {
1112 return new SavedState[size];
1118 public Parcelable onSaveInstanceState() {
1119 // Force our ancestor class to save its state
1120 Parcelable superState = super.onSaveInstanceState();
1121 SavedState ss = new SavedState(superState);
1123 ss.progress = mProgress;
1124 ss.secondaryProgress = mSecondaryProgress;
1130 public void onRestoreInstanceState(Parcelable state) {
1131 SavedState ss = (SavedState) state;
1132 super.onRestoreInstanceState(ss.getSuperState());
1134 setProgress(ss.progress);
1135 setSecondaryProgress(ss.secondaryProgress);
1139 protected void onAttachedToWindow() {
1140 super.onAttachedToWindow();
1141 if (mIndeterminate) {
1147 protected void onDetachedFromWindow() {
1148 if (mIndeterminate) {
1151 if(mRefreshProgressRunnable != null) {
1152 removeCallbacks(mRefreshProgressRunnable);
1154 if (mAccessibilityEventSender != null) {
1155 removeCallbacks(mAccessibilityEventSender);
1157 // This should come after stopAnimation(), otherwise an invalidate message remains in the
1158 // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
1159 super.onDetachedFromWindow();
1163 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1164 super.onInitializeAccessibilityEvent(event);
1165 event.setItemCount(mMax);
1166 event.setCurrentItemIndex(mProgress);
1170 * Schedule a command for sending an accessibility event.
1172 * Note: A command is used to ensure that accessibility events
1173 * are sent at most one in a given time frame to save
1174 * system resources while the progress changes quickly.
1176 private void scheduleAccessibilityEventSender() {
1177 if (mAccessibilityEventSender == null) {
1178 mAccessibilityEventSender = new AccessibilityEventSender();
1180 removeCallbacks(mAccessibilityEventSender);
1182 postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
1186 * Command for sending an accessibility event.
1188 private class AccessibilityEventSender implements Runnable {
1190 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);