blob: 9c4264d1c880d178f134fdac9567340b906afcc2 [file] [log] [blame]
Marco Nelissenf568b602011-06-28 08:52:55 -07001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17package com.android.musicfx.seekbar;
18
19import com.android.internal.R;
20
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.graphics.Bitmap;
24import android.graphics.BitmapShader;
25import android.graphics.Canvas;
26import android.graphics.Rect;
27import android.graphics.Shader;
28import android.graphics.drawable.Animatable;
29import android.graphics.drawable.AnimationDrawable;
30import android.graphics.drawable.BitmapDrawable;
31import android.graphics.drawable.ClipDrawable;
32import android.graphics.drawable.Drawable;
33import android.graphics.drawable.LayerDrawable;
34import android.graphics.drawable.ShapeDrawable;
35import android.graphics.drawable.StateListDrawable;
36import android.graphics.drawable.shapes.RoundRectShape;
37import android.graphics.drawable.shapes.Shape;
38import android.os.Parcel;
39import android.os.Parcelable;
40import android.os.SystemClock;
41import android.util.AttributeSet;
42import android.view.Gravity;
43import android.view.RemotableViewMethod;
44import android.view.View;
45import android.view.ViewDebug;
46import android.view.accessibility.AccessibilityEvent;
47import android.view.accessibility.AccessibilityManager;
48import android.view.animation.AlphaAnimation;
49import android.view.animation.Animation;
50import android.view.animation.AnimationUtils;
51import android.view.animation.Interpolator;
52import android.view.animation.LinearInterpolator;
53import android.view.animation.Transformation;
54import android.widget.RemoteViews.RemoteView;
55
56
57/**
58 * <p>
59 * Visual indicator of progress in some operation. Displays a bar to the user
60 * representing how far the operation has progressed; the application can
61 * change the amount of progress (modifying the length of the bar) as it moves
62 * forward. There is also a secondary progress displayable on a progress bar
63 * which is useful for displaying intermediate progress, such as the buffer
64 * level during a streaming playback progress bar.
65 * </p>
66 *
67 * <p>
68 * A progress bar can also be made indeterminate. In indeterminate mode, the
69 * progress bar shows a cyclic animation without an indication of progress. This mode is used by
70 * applications when the length of the task is unknown. The indeterminate progress bar can be either
71 * a spinning wheel or a horizontal bar.
72 * </p>
73 *
74 * <p>The following code example shows how a progress bar can be used from
75 * a worker thread to update the user interface to notify the user of progress:
76 * </p>
77 *
78 * <pre>
79 * public class MyActivity extends Activity {
80 * private static final int PROGRESS = 0x1;
81 *
82 * private ProgressBar mProgress;
83 * private int mProgressStatus = 0;
84 *
85 * private Handler mHandler = new Handler();
86 *
87 * protected void onCreate(Bundle icicle) {
88 * super.onCreate(icicle);
89 *
90 * setContentView(R.layout.progressbar_activity);
91 *
92 * mProgress = (ProgressBar) findViewById(R.id.progress_bar);
93 *
94 * // Start lengthy operation in a background thread
95 * new Thread(new Runnable() {
96 * public void run() {
97 * while (mProgressStatus &lt; 100) {
98 * mProgressStatus = doWork();
99 *
100 * // Update the progress bar
101 * mHandler.post(new Runnable() {
102 * public void run() {
103 * mProgress.setProgress(mProgressStatus);
104 * }
105 * });
106 * }
107 * }
108 * }).start();
109 * }
110 * }</pre>
111 *
112 * <p>To add a progress bar to a layout file, you can use the {@code &lt;ProgressBar&gt;} element.
113 * By default, the progress bar is a spinning wheel (an indeterminate indicator). To change to a
114 * horizontal progress bar, apply the {@link android.R.style#Widget_ProgressBar_Horizontal
115 * Widget.ProgressBar.Horizontal} style, like so:</p>
116 *
117 * <pre>
118 * &lt;ProgressBar
119 * style="@android:style/Widget.ProgressBar.Horizontal"
120 * ... /&gt;</pre>
121 *
122 * <p>If you will use the progress bar to show real progress, you must use the horizontal bar. You
123 * can then increment the progress with {@link #incrementProgressBy incrementProgressBy()} or
124 * {@link #setProgress setProgress()}. By default, the progress bar is full when it reaches 100. If
125 * necessary, you can adjust the maximum value (the value for a full bar) using the {@link
126 * android.R.styleable#ProgressBar_max android:max} attribute. Other attributes available are listed
127 * below.</p>
128 *
129 * <p>Another common style to apply to the progress bar is {@link
130 * android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}, which shows a smaller
131 * version of the spinning wheel&mdash;useful when waiting for content to load.
132 * For example, you can insert this kind of progress bar into your default layout for
133 * a view that will be populated by some content fetched from the Internet&mdash;the spinning wheel
134 * appears immediately and when your application receives the content, it replaces the progress bar
135 * with the loaded content. For example:</p>
136 *
137 * <pre>
138 * &lt;LinearLayout
139 * android:orientation="horizontal"
140 * ... &gt;
141 * &lt;ProgressBar
142 * android:layout_width="wrap_content"
143 * android:layout_height="wrap_content"
144 * style="@android:style/Widget.ProgressBar.Small"
145 * android:layout_marginRight="5dp" /&gt;
146 * &lt;TextView
147 * android:layout_width="wrap_content"
148 * android:layout_height="wrap_content"
149 * android:text="@string/loading" /&gt;
150 * &lt;/LinearLayout&gt;</pre>
151 *
152 * <p>Other progress bar styles provided by the system include:</p>
153 * <ul>
154 * <li>{@link android.R.style#Widget_ProgressBar_Horizontal Widget.ProgressBar.Horizontal}</li>
155 * <li>{@link android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}</li>
156 * <li>{@link android.R.style#Widget_ProgressBar_Large Widget.ProgressBar.Large}</li>
157 * <li>{@link android.R.style#Widget_ProgressBar_Inverse Widget.ProgressBar.Inverse}</li>
158 * <li>{@link android.R.style#Widget_ProgressBar_Small_Inverse
159 * Widget.ProgressBar.Small.Inverse}</li>
160 * <li>{@link android.R.style#Widget_ProgressBar_Large_Inverse
161 * Widget.ProgressBar.Large.Inverse}</li>
162 * </ul>
163 * <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary
164 * if your application uses a light colored theme (a white background).</p>
165 *
166 * <p><strong>XML attributes</b></strong>
167 * <p>
168 * See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
169 * {@link android.R.styleable#View View Attributes}
170 * </p>
171 *
172 * @attr ref android.R.styleable#ProgressBar_animationResolution
173 * @attr ref android.R.styleable#ProgressBar_indeterminate
174 * @attr ref android.R.styleable#ProgressBar_indeterminateBehavior
175 * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable
176 * @attr ref android.R.styleable#ProgressBar_indeterminateDuration
177 * @attr ref android.R.styleable#ProgressBar_indeterminateOnly
178 * @attr ref android.R.styleable#ProgressBar_interpolator
179 * @attr ref android.R.styleable#ProgressBar_max
180 * @attr ref android.R.styleable#ProgressBar_maxHeight
181 * @attr ref android.R.styleable#ProgressBar_maxWidth
182 * @attr ref android.R.styleable#ProgressBar_minHeight
183 * @attr ref android.R.styleable#ProgressBar_minWidth
184 * @attr ref android.R.styleable#ProgressBar_progress
185 * @attr ref android.R.styleable#ProgressBar_progressDrawable
186 * @attr ref android.R.styleable#ProgressBar_secondaryProgress
187 */
188@RemoteView
189public class ProgressBar extends View {
190 private static final int MAX_LEVEL = 10000;
191 private static final int ANIMATION_RESOLUTION = 200;
192 private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200;
193
194 int mMinWidth;
195 int mMaxWidth;
196 int mMinHeight;
197 int mMaxHeight;
198
199 private int mProgress;
200 private int mSecondaryProgress;
201 private int mMax;
202
203 private int mBehavior;
204 private int mDuration;
205 private boolean mIndeterminate;
206 private boolean mOnlyIndeterminate;
207 private Transformation mTransformation;
208 private AlphaAnimation mAnimation;
209 private Drawable mIndeterminateDrawable;
210 private Drawable mProgressDrawable;
211 private Drawable mCurrentDrawable;
212 Bitmap mSampleTile;
213 private boolean mNoInvalidate;
214 private Interpolator mInterpolator;
215 private RefreshProgressRunnable mRefreshProgressRunnable;
216 private long mUiThreadId;
217 private boolean mShouldStartAnimationDrawable;
218 private long mLastDrawTime;
219
220 private boolean mInDrawing;
221
222 private int mAnimationResolution;
223
224 private AccessibilityEventSender mAccessibilityEventSender;
225
226 /**
227 * Create a new progress bar with range 0...100 and initial progress of 0.
228 * @param context the application environment
229 */
230 public ProgressBar(Context context) {
231 this(context, null);
232 }
233
234 public ProgressBar(Context context, AttributeSet attrs) {
235 this(context, attrs, com.android.internal.R.attr.progressBarStyle);
236 }
237
238 public ProgressBar(Context context, AttributeSet attrs, int defStyle) {
239 this(context, attrs, defStyle, 0);
240 }
241
242 /**
243 * @hide
244 */
245 public ProgressBar(Context context, AttributeSet attrs, int defStyle, int styleRes) {
246 super(context, attrs, defStyle);
247 mUiThreadId = Thread.currentThread().getId();
248 initProgressBar();
249
250 TypedArray a =
251 context.obtainStyledAttributes(attrs, R.styleable.ProgressBar, defStyle, styleRes);
252
253 mNoInvalidate = true;
254
255 Drawable drawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
256 if (drawable != null) {
257 drawable = tileify(drawable, false);
258 // Calling this method can set mMaxHeight, make sure the corresponding
259 // XML attribute for mMaxHeight is read after calling this method
260 setProgressDrawable(drawable);
261 }
262
263
264 mDuration = a.getInt(R.styleable.ProgressBar_indeterminateDuration, mDuration);
265
266 mMinWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_minWidth, mMinWidth);
267 mMaxWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_maxWidth, mMaxWidth);
268 mMinHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_minHeight, mMinHeight);
269 mMaxHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_maxHeight, mMaxHeight);
270
271 mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior);
272
273 final int resID = a.getResourceId(
274 com.android.internal.R.styleable.ProgressBar_interpolator,
275 android.R.anim.linear_interpolator); // default to linear interpolator
276 if (resID > 0) {
277 setInterpolator(context, resID);
278 }
279
280 setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
281
282 setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
283
284 setSecondaryProgress(
285 a.getInt(R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
286
287 drawable = a.getDrawable(R.styleable.ProgressBar_indeterminateDrawable);
288 if (drawable != null) {
289 drawable = tileifyIndeterminate(drawable);
290 setIndeterminateDrawable(drawable);
291 }
292
293 mOnlyIndeterminate = a.getBoolean(
294 R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate);
295
296 mNoInvalidate = false;
297
298 setIndeterminate(mOnlyIndeterminate || a.getBoolean(
299 R.styleable.ProgressBar_indeterminate, mIndeterminate));
300
301 mAnimationResolution = a.getInteger(R.styleable.ProgressBar_animationResolution,
302 ANIMATION_RESOLUTION);
303
304 a.recycle();
305 }
306
307 /**
308 * Converts a drawable to a tiled version of itself. It will recursively
309 * traverse layer and state list drawables.
310 */
311 private Drawable tileify(Drawable drawable, boolean clip) {
312
313 if (drawable instanceof LayerDrawable) {
314 LayerDrawable background = (LayerDrawable) drawable;
315 final int N = background.getNumberOfLayers();
316 Drawable[] outDrawables = new Drawable[N];
317
318 for (int i = 0; i < N; i++) {
319 int id = background.getId(i);
320 outDrawables[i] = tileify(background.getDrawable(i),
321 (id == R.id.progress || id == R.id.secondaryProgress));
322 }
323
324 LayerDrawable newBg = new LayerDrawable(outDrawables);
325
326 for (int i = 0; i < N; i++) {
327 newBg.setId(i, background.getId(i));
328 }
329
330 return newBg;
331
332 } else if (drawable instanceof StateListDrawable) {
333 StateListDrawable in = (StateListDrawable) drawable;
334 StateListDrawable out = new StateListDrawable();
335 int numStates = in.getStateCount();
336 for (int i = 0; i < numStates; i++) {
337 out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
338 }
339 return out;
340
341 } else if (drawable instanceof BitmapDrawable) {
342 final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
343 if (mSampleTile == null) {
344 mSampleTile = tileBitmap;
345 }
346
347 final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
348
349 final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
350 Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
351 shapeDrawable.getPaint().setShader(bitmapShader);
352
353 return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
354 ClipDrawable.HORIZONTAL) : shapeDrawable;
355 }
356
357 return drawable;
358 }
359
360 Shape getDrawableShape() {
361 final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
362 return new RoundRectShape(roundedCorners, null, null);
363 }
364
365 /**
366 * Convert a AnimationDrawable for use as a barberpole animation.
367 * Each frame of the animation is wrapped in a ClipDrawable and
368 * given a tiling BitmapShader.
369 */
370 private Drawable tileifyIndeterminate(Drawable drawable) {
371 if (drawable instanceof AnimationDrawable) {
372 AnimationDrawable background = (AnimationDrawable) drawable;
373 final int N = background.getNumberOfFrames();
374 AnimationDrawable newBg = new AnimationDrawable();
375 newBg.setOneShot(background.isOneShot());
376
377 for (int i = 0; i < N; i++) {
378 Drawable frame = tileify(background.getFrame(i), true);
379 frame.setLevel(10000);
380 newBg.addFrame(frame, background.getDuration(i));
381 }
382 newBg.setLevel(10000);
383 drawable = newBg;
384 }
385 return drawable;
386 }
387
388 /**
389 * <p>
390 * Initialize the progress bar's default values:
391 * </p>
392 * <ul>
393 * <li>progress = 0</li>
394 * <li>max = 100</li>
395 * <li>animation duration = 4000 ms</li>
396 * <li>indeterminate = false</li>
397 * <li>behavior = repeat</li>
398 * </ul>
399 */
400 private void initProgressBar() {
401 mMax = 100;
402 mProgress = 0;
403 mSecondaryProgress = 0;
404 mIndeterminate = false;
405 mOnlyIndeterminate = false;
406 mDuration = 4000;
407 mBehavior = AlphaAnimation.RESTART;
408 mMinWidth = 24;
409 mMaxWidth = 48;
410 mMinHeight = 24;
411 mMaxHeight = 48;
412 }
413
414 /**
415 * <p>Indicate whether this progress bar is in indeterminate mode.</p>
416 *
417 * @return true if the progress bar is in indeterminate mode
418 */
419 @ViewDebug.ExportedProperty(category = "progress")
420 public synchronized boolean isIndeterminate() {
421 return mIndeterminate;
422 }
423
424 /**
425 * <p>Change the indeterminate mode for this progress bar. In indeterminate
426 * mode, the progress is ignored and the progress bar shows an infinite
427 * animation instead.</p>
428 *
429 * If this progress bar's style only supports indeterminate mode (such as the circular
430 * progress bars), then this will be ignored.
431 *
432 * @param indeterminate true to enable the indeterminate mode
433 */
434 @android.view.RemotableViewMethod
435 public synchronized void setIndeterminate(boolean indeterminate) {
436 if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
437 mIndeterminate = indeterminate;
438
439 if (indeterminate) {
440 // swap between indeterminate and regular backgrounds
441 mCurrentDrawable = mIndeterminateDrawable;
442 startAnimation();
443 } else {
444 mCurrentDrawable = mProgressDrawable;
445 stopAnimation();
446 }
447 }
448 }
449
450 /**
451 * <p>Get the drawable used to draw the progress bar in
452 * indeterminate mode.</p>
453 *
454 * @return a {@link android.graphics.drawable.Drawable} instance
455 *
456 * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
457 * @see #setIndeterminate(boolean)
458 */
459 public Drawable getIndeterminateDrawable() {
460 return mIndeterminateDrawable;
461 }
462
463 /**
464 * <p>Define the drawable used to draw the progress bar in
465 * indeterminate mode.</p>
466 *
467 * @param d the new drawable
468 *
469 * @see #getIndeterminateDrawable()
470 * @see #setIndeterminate(boolean)
471 */
472 public void setIndeterminateDrawable(Drawable d) {
473 if (d != null) {
474 d.setCallback(this);
475 }
476 mIndeterminateDrawable = d;
477 if (mIndeterminate) {
478 mCurrentDrawable = d;
479 postInvalidate();
480 }
481 }
482
483 /**
484 * <p>Get the drawable used to draw the progress bar in
485 * progress mode.</p>
486 *
487 * @return a {@link android.graphics.drawable.Drawable} instance
488 *
489 * @see #setProgressDrawable(android.graphics.drawable.Drawable)
490 * @see #setIndeterminate(boolean)
491 */
492 public Drawable getProgressDrawable() {
493 return mProgressDrawable;
494 }
495
496 /**
497 * <p>Define the drawable used to draw the progress bar in
498 * progress mode.</p>
499 *
500 * @param d the new drawable
501 *
502 * @see #getProgressDrawable()
503 * @see #setIndeterminate(boolean)
504 */
505 public void setProgressDrawable(Drawable d) {
506 boolean needUpdate;
507 if (mProgressDrawable != null && d != mProgressDrawable) {
508 mProgressDrawable.setCallback(null);
509 needUpdate = true;
510 } else {
511 needUpdate = false;
512 }
513
514 if (d != null) {
515 d.setCallback(this);
516
517 // Make sure the ProgressBar is always tall enough
518 int drawableHeight = d.getMinimumHeight();
519 if (mMaxHeight < drawableHeight) {
520 mMaxHeight = drawableHeight;
521 requestLayout();
522 }
523 }
524 mProgressDrawable = d;
525 if (!mIndeterminate) {
526 mCurrentDrawable = d;
527 postInvalidate();
528 }
529
530 if (needUpdate) {
531 updateDrawableBounds(getWidth(), getHeight());
532 updateDrawableState();
533 doRefreshProgress(R.id.progress, mProgress, false, false);
534 doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false);
535 }
536 }
537
538 /**
539 * @return The drawable currently used to draw the progress bar
540 */
541 Drawable getCurrentDrawable() {
542 return mCurrentDrawable;
543 }
544
545 @Override
546 protected boolean verifyDrawable(Drawable who) {
547 return who == mProgressDrawable || who == mIndeterminateDrawable
548 || super.verifyDrawable(who);
549 }
550
551 @Override
552 public void jumpDrawablesToCurrentState() {
553 super.jumpDrawablesToCurrentState();
554 if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
555 if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
556 }
557
558 @Override
559 public void postInvalidate() {
560 if (!mNoInvalidate) {
561 super.postInvalidate();
562 }
563 }
564
565 private class RefreshProgressRunnable implements Runnable {
566
567 private int mId;
568 private int mProgress;
569 private boolean mFromUser;
570
571 RefreshProgressRunnable(int id, int progress, boolean fromUser) {
572 mId = id;
573 mProgress = progress;
574 mFromUser = fromUser;
575 }
576
577 public void run() {
578 doRefreshProgress(mId, mProgress, mFromUser, true);
579 // Put ourselves back in the cache when we are done
580 mRefreshProgressRunnable = this;
581 }
582
583 public void setup(int id, int progress, boolean fromUser) {
584 mId = id;
585 mProgress = progress;
586 mFromUser = fromUser;
587 }
588
589 }
590
591 private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
592 boolean callBackToApp) {
593 float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
594 final Drawable d = mCurrentDrawable;
595 if (d != null) {
596 Drawable progressDrawable = null;
597
598 if (d instanceof LayerDrawable) {
599 progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
600 }
601
602 final int level = (int) (scale * MAX_LEVEL);
603 (progressDrawable != null ? progressDrawable : d).setLevel(level);
604 } else {
605 invalidate();
606 }
607
608 if (callBackToApp && id == R.id.progress) {
609 onProgressRefresh(scale, fromUser);
610 }
611 }
612
613 void onProgressRefresh(float scale, boolean fromUser) {
614 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
615 scheduleAccessibilityEventSender();
616 }
617 }
618
619 private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
620 if (mUiThreadId == Thread.currentThread().getId()) {
621 doRefreshProgress(id, progress, fromUser, true);
622 } else {
623 RefreshProgressRunnable r;
624 if (mRefreshProgressRunnable != null) {
625 // Use cached RefreshProgressRunnable if available
626 r = mRefreshProgressRunnable;
627 // Uncache it
628 mRefreshProgressRunnable = null;
629 r.setup(id, progress, fromUser);
630 } else {
631 // Make a new one
632 r = new RefreshProgressRunnable(id, progress, fromUser);
633 }
634 post(r);
635 }
636 }
637
638 /**
639 * <p>Set the current progress to the specified value. Does not do anything
640 * if the progress bar is in indeterminate mode.</p>
641 *
642 * @param progress the new progress, between 0 and {@link #getMax()}
643 *
644 * @see #setIndeterminate(boolean)
645 * @see #isIndeterminate()
646 * @see #getProgress()
647 * @see #incrementProgressBy(int)
648 */
649 @android.view.RemotableViewMethod
650 public synchronized void setProgress(int progress) {
651 setProgress(progress, false);
652 }
653
654 @android.view.RemotableViewMethod
655 synchronized void setProgress(int progress, boolean fromUser) {
656 if (mIndeterminate) {
657 return;
658 }
659
660 if (progress < 0) {
661 progress = 0;
662 }
663
664 if (progress > mMax) {
665 progress = mMax;
666 }
667
668 if (progress != mProgress) {
669 mProgress = progress;
670 refreshProgress(R.id.progress, mProgress, fromUser);
671 }
672 }
673
674 /**
675 * <p>
676 * Set the current secondary progress to the specified value. Does not do
677 * anything if the progress bar is in indeterminate mode.
678 * </p>
679 *
680 * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
681 * @see #setIndeterminate(boolean)
682 * @see #isIndeterminate()
683 * @see #getSecondaryProgress()
684 * @see #incrementSecondaryProgressBy(int)
685 */
686 @android.view.RemotableViewMethod
687 public synchronized void setSecondaryProgress(int secondaryProgress) {
688 if (mIndeterminate) {
689 return;
690 }
691
692 if (secondaryProgress < 0) {
693 secondaryProgress = 0;
694 }
695
696 if (secondaryProgress > mMax) {
697 secondaryProgress = mMax;
698 }
699
700 if (secondaryProgress != mSecondaryProgress) {
701 mSecondaryProgress = secondaryProgress;
702 refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false);
703 }
704 }
705
706 /**
707 * <p>Get the progress bar's current level of progress. Return 0 when the
708 * progress bar is in indeterminate mode.</p>
709 *
710 * @return the current progress, between 0 and {@link #getMax()}
711 *
712 * @see #setIndeterminate(boolean)
713 * @see #isIndeterminate()
714 * @see #setProgress(int)
715 * @see #setMax(int)
716 * @see #getMax()
717 */
718 @ViewDebug.ExportedProperty(category = "progress")
719 public synchronized int getProgress() {
720 return mIndeterminate ? 0 : mProgress;
721 }
722
723 /**
724 * <p>Get the progress bar's current level of secondary progress. Return 0 when the
725 * progress bar is in indeterminate mode.</p>
726 *
727 * @return the current secondary progress, between 0 and {@link #getMax()}
728 *
729 * @see #setIndeterminate(boolean)
730 * @see #isIndeterminate()
731 * @see #setSecondaryProgress(int)
732 * @see #setMax(int)
733 * @see #getMax()
734 */
735 @ViewDebug.ExportedProperty(category = "progress")
736 public synchronized int getSecondaryProgress() {
737 return mIndeterminate ? 0 : mSecondaryProgress;
738 }
739
740 /**
741 * <p>Return the upper limit of this progress bar's range.</p>
742 *
743 * @return a positive integer
744 *
745 * @see #setMax(int)
746 * @see #getProgress()
747 * @see #getSecondaryProgress()
748 */
749 @ViewDebug.ExportedProperty(category = "progress")
750 public synchronized int getMax() {
751 return mMax;
752 }
753
754 /**
755 * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
756 *
757 * @param max the upper range of this progress bar
758 *
759 * @see #getMax()
760 * @see #setProgress(int)
761 * @see #setSecondaryProgress(int)
762 */
763 @android.view.RemotableViewMethod
764 public synchronized void setMax(int max) {
765 if (max < 0) {
766 max = 0;
767 }
768 if (max != mMax) {
769 mMax = max;
770 postInvalidate();
771
772 if (mProgress > max) {
773 mProgress = max;
774 }
775 refreshProgress(R.id.progress, mProgress, false);
776 }
777 }
778
779 /**
780 * <p>Increase the progress bar's progress by the specified amount.</p>
781 *
782 * @param diff the amount by which the progress must be increased
783 *
784 * @see #setProgress(int)
785 */
786 public synchronized final void incrementProgressBy(int diff) {
787 setProgress(mProgress + diff);
788 }
789
790 /**
791 * <p>Increase the progress bar's secondary progress by the specified amount.</p>
792 *
793 * @param diff the amount by which the secondary progress must be increased
794 *
795 * @see #setSecondaryProgress(int)
796 */
797 public synchronized final void incrementSecondaryProgressBy(int diff) {
798 setSecondaryProgress(mSecondaryProgress + diff);
799 }
800
801 /**
802 * <p>Start the indeterminate progress animation.</p>
803 */
804 void startAnimation() {
805 if (getVisibility() != VISIBLE) {
806 return;
807 }
808
809 if (mIndeterminateDrawable instanceof Animatable) {
810 mShouldStartAnimationDrawable = true;
811 mAnimation = null;
812 } else {
813 if (mInterpolator == null) {
814 mInterpolator = new LinearInterpolator();
815 }
816
817 mTransformation = new Transformation();
818 mAnimation = new AlphaAnimation(0.0f, 1.0f);
819 mAnimation.setRepeatMode(mBehavior);
820 mAnimation.setRepeatCount(Animation.INFINITE);
821 mAnimation.setDuration(mDuration);
822 mAnimation.setInterpolator(mInterpolator);
823 mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
824 }
825 postInvalidate();
826 }
827
828 /**
829 * <p>Stop the indeterminate progress animation.</p>
830 */
831 void stopAnimation() {
832 mAnimation = null;
833 mTransformation = null;
834 if (mIndeterminateDrawable instanceof Animatable) {
835 ((Animatable) mIndeterminateDrawable).stop();
836 mShouldStartAnimationDrawable = false;
837 }
838 postInvalidate();
839 }
840
841 /**
842 * Sets the acceleration curve for the indeterminate animation.
843 * The interpolator is loaded as a resource from the specified context.
844 *
845 * @param context The application environment
846 * @param resID The resource identifier of the interpolator to load
847 */
848 public void setInterpolator(Context context, int resID) {
849 setInterpolator(AnimationUtils.loadInterpolator(context, resID));
850 }
851
852 /**
853 * Sets the acceleration curve for the indeterminate animation.
854 * Defaults to a linear interpolation.
855 *
856 * @param interpolator The interpolator which defines the acceleration curve
857 */
858 public void setInterpolator(Interpolator interpolator) {
859 mInterpolator = interpolator;
860 }
861
862 /**
863 * Gets the acceleration curve type for the indeterminate animation.
864 *
865 * @return the {@link Interpolator} associated to this animation
866 */
867 public Interpolator getInterpolator() {
868 return mInterpolator;
869 }
870
871 @Override
872 @RemotableViewMethod
873 public void setVisibility(int v) {
874 if (getVisibility() != v) {
875 super.setVisibility(v);
876
877 if (mIndeterminate) {
878 // let's be nice with the UI thread
879 if (v == GONE || v == INVISIBLE) {
880 stopAnimation();
881 } else {
882 startAnimation();
883 }
884 }
885 }
886 }
887
888 @Override
889 protected void onVisibilityChanged(View changedView, int visibility) {
890 super.onVisibilityChanged(changedView, visibility);
891
892 if (mIndeterminate) {
893 // let's be nice with the UI thread
894 if (visibility == GONE || visibility == INVISIBLE) {
895 stopAnimation();
896 } else {
897 startAnimation();
898 }
899 }
900 }
901
902 @Override
903 public void invalidateDrawable(Drawable dr) {
904 if (!mInDrawing) {
905 if (verifyDrawable(dr)) {
906 final Rect dirty = dr.getBounds();
907 final int scrollX = mScrollX + mPaddingLeft;
908 final int scrollY = mScrollY + mPaddingTop;
909
910 invalidate(dirty.left + scrollX, dirty.top + scrollY,
911 dirty.right + scrollX, dirty.bottom + scrollY);
912 } else {
913 super.invalidateDrawable(dr);
914 }
915 }
916 }
917
Marco Nelissenf568b602011-06-28 08:52:55 -0700918 @Override
919 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
920 updateDrawableBounds(w, h);
921 }
922
923 private void updateDrawableBounds(int w, int h) {
924 // onDraw will translate the canvas so we draw starting at 0,0
925 int right = w - mPaddingRight - mPaddingLeft;
926 int bottom = h - mPaddingBottom - mPaddingTop;
Marco Nelissen0962a4b2011-12-08 14:53:23 -0800927 int top = 0;
928 int left = 0;
Marco Nelissenf568b602011-06-28 08:52:55 -0700929
930 if (mIndeterminateDrawable != null) {
Marco Nelissen0962a4b2011-12-08 14:53:23 -0800931 // Aspect ratio logic does not apply to AnimationDrawables
932 if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
933 // Maintain aspect ratio. Certain kinds of animated drawables
934 // get very confused otherwise.
935 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
936 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
937 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
938 final float boundAspect = (float) w / h;
939 if (intrinsicAspect != boundAspect) {
940 if (boundAspect > intrinsicAspect) {
941 // New width is larger. Make it smaller to match height.
942 final int width = (int) (h * intrinsicAspect);
943 left = (w - width) / 2;
944 right = left + width;
945 } else {
946 // New height is larger. Make it smaller to match width.
947 final int height = (int) (w * (1 / intrinsicAspect));
948 top = (h - height) / 2;
949 bottom = top + height;
950 }
951 }
952 }
953 mIndeterminateDrawable.setBounds(left, top, right, bottom);
Marco Nelissenf568b602011-06-28 08:52:55 -0700954 }
955
956 if (mProgressDrawable != null) {
957 mProgressDrawable.setBounds(0, 0, right, bottom);
958 }
959 }
960
961 @Override
962 protected synchronized void onDraw(Canvas canvas) {
963 super.onDraw(canvas);
964
965 Drawable d = mCurrentDrawable;
966 if (d != null) {
967 // Translate canvas so a indeterminate circular progress bar with padding
968 // rotates properly in its animation
969 canvas.save();
970 canvas.translate(mPaddingLeft, mPaddingTop);
971 long time = getDrawingTime();
972 if (mAnimation != null) {
973 mAnimation.getTransformation(time, mTransformation);
974 float scale = mTransformation.getAlpha();
975 try {
976 mInDrawing = true;
977 d.setLevel((int) (scale * MAX_LEVEL));
978 } finally {
979 mInDrawing = false;
980 }
981 if (SystemClock.uptimeMillis() - mLastDrawTime >= mAnimationResolution) {
982 mLastDrawTime = SystemClock.uptimeMillis();
983 postInvalidateDelayed(mAnimationResolution);
984 }
985 }
986 d.draw(canvas);
987 canvas.restore();
988 if (mShouldStartAnimationDrawable && d instanceof Animatable) {
989 ((Animatable) d).start();
990 mShouldStartAnimationDrawable = false;
991 }
992 }
993 }
994
995 @Override
996 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
997 Drawable d = mCurrentDrawable;
998
999 int dw = 0;
1000 int dh = 0;
1001 if (d != null) {
1002 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
1003 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
1004 }
1005 updateDrawableState();
1006 dw += mPaddingLeft + mPaddingRight;
1007 dh += mPaddingTop + mPaddingBottom;
1008
1009 setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
1010 resolveSizeAndState(dh, heightMeasureSpec, 0));
1011 }
1012
1013 @Override
1014 protected void drawableStateChanged() {
1015 super.drawableStateChanged();
1016 updateDrawableState();
1017 }
1018
1019 private void updateDrawableState() {
1020 int[] state = getDrawableState();
1021
1022 if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
1023 mProgressDrawable.setState(state);
1024 }
1025
1026 if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
1027 mIndeterminateDrawable.setState(state);
1028 }
1029 }
1030
1031 static class SavedState extends BaseSavedState {
1032 int progress;
1033 int secondaryProgress;
1034
1035 /**
1036 * Constructor called from {@link ProgressBar#onSaveInstanceState()}
1037 */
1038 SavedState(Parcelable superState) {
1039 super(superState);
1040 }
1041
1042 /**
1043 * Constructor called from {@link #CREATOR}
1044 */
1045 private SavedState(Parcel in) {
1046 super(in);
1047 progress = in.readInt();
1048 secondaryProgress = in.readInt();
1049 }
1050
1051 @Override
1052 public void writeToParcel(Parcel out, int flags) {
1053 super.writeToParcel(out, flags);
1054 out.writeInt(progress);
1055 out.writeInt(secondaryProgress);
1056 }
1057
1058 public static final Parcelable.Creator<SavedState> CREATOR
1059 = new Parcelable.Creator<SavedState>() {
1060 public SavedState createFromParcel(Parcel in) {
1061 return new SavedState(in);
1062 }
1063
1064 public SavedState[] newArray(int size) {
1065 return new SavedState[size];
1066 }
1067 };
1068 }
1069
1070 @Override
1071 public Parcelable onSaveInstanceState() {
1072 // Force our ancestor class to save its state
1073 Parcelable superState = super.onSaveInstanceState();
1074 SavedState ss = new SavedState(superState);
1075
1076 ss.progress = mProgress;
1077 ss.secondaryProgress = mSecondaryProgress;
1078
1079 return ss;
1080 }
1081
1082 @Override
1083 public void onRestoreInstanceState(Parcelable state) {
1084 SavedState ss = (SavedState) state;
1085 super.onRestoreInstanceState(ss.getSuperState());
1086
1087 setProgress(ss.progress);
1088 setSecondaryProgress(ss.secondaryProgress);
1089 }
1090
1091 @Override
1092 protected void onAttachedToWindow() {
1093 super.onAttachedToWindow();
1094 if (mIndeterminate) {
1095 startAnimation();
1096 }
1097 }
1098
1099 @Override
1100 protected void onDetachedFromWindow() {
1101 if (mIndeterminate) {
1102 stopAnimation();
1103 }
1104 if(mRefreshProgressRunnable != null) {
1105 removeCallbacks(mRefreshProgressRunnable);
1106 }
1107 if (mAccessibilityEventSender != null) {
1108 removeCallbacks(mAccessibilityEventSender);
1109 }
1110 // This should come after stopAnimation(), otherwise an invalidate message remains in the
1111 // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
1112 super.onDetachedFromWindow();
1113 }
1114
1115 @Override
1116 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1117 super.onInitializeAccessibilityEvent(event);
1118 event.setItemCount(mMax);
1119 event.setCurrentItemIndex(mProgress);
1120 }
1121
1122 /**
1123 * Schedule a command for sending an accessibility event.
1124 * </br>
1125 * Note: A command is used to ensure that accessibility events
1126 * are sent at most one in a given time frame to save
1127 * system resources while the progress changes quickly.
1128 */
1129 private void scheduleAccessibilityEventSender() {
1130 if (mAccessibilityEventSender == null) {
1131 mAccessibilityEventSender = new AccessibilityEventSender();
1132 } else {
1133 removeCallbacks(mAccessibilityEventSender);
1134 }
1135 postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
1136 }
1137
1138 /**
1139 * Command for sending an accessibility event.
1140 */
1141 private class AccessibilityEventSender implements Runnable {
1142 public void run() {
1143 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
1144 }
1145 }
1146}