DrawableContainer.java revision f2a47782f31b58d2d31bd00b50fe43604af8b9c2
1/*
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 android.graphics.drawable;
18
19import android.content.res.Resources;
20import android.graphics.Canvas;
21import android.graphics.ColorFilter;
22import android.graphics.PixelFormat;
23import android.graphics.Rect;
24import android.os.SystemClock;
25
26/**
27 * A helper class that contains several {@link Drawable}s and selects which one to use.
28 *
29 * You can subclass it to create your own DrawableContainers or directly use one its child classes.
30 */
31public class DrawableContainer extends Drawable implements Drawable.Callback {
32    private static final boolean DEBUG = false;
33    private static final String TAG = "DrawableContainer";
34
35    /**
36     * To be proper, we should have a getter for dither (and alpha, etc.)
37     * so that proxy classes like this can save/restore their delegates'
38     * values, but we don't have getters. Since we do have setters
39     * (e.g. setDither), which this proxy forwards on, we have to have some
40     * default/initial setting.
41     *
42     * The initial setting for dither is now true, since it almost always seems
43     * to improve the quality at negligible cost.
44     */
45    private static final boolean DEFAULT_DITHER = true;
46    private DrawableContainerState mDrawableContainerState;
47    private Drawable mCurrDrawable;
48    private int mAlpha = 0xFF;
49    private ColorFilter mColorFilter;
50
51    private int mCurIndex = -1;
52    private boolean mMutated;
53
54    // Animations.
55    private Runnable mAnimationRunnable;
56    private long mEnterAnimationEnd;
57    private long mExitAnimationEnd;
58    private Drawable mLastDrawable;
59
60    // overrides from Drawable
61
62    @Override
63    public void draw(Canvas canvas) {
64        if (mCurrDrawable != null) {
65            mCurrDrawable.draw(canvas);
66        }
67        if (mLastDrawable != null) {
68            mLastDrawable.draw(canvas);
69        }
70    }
71
72    @Override
73    public int getChangingConfigurations() {
74        return super.getChangingConfigurations()
75                | mDrawableContainerState.mChangingConfigurations
76                | mDrawableContainerState.mChildrenChangingConfigurations;
77    }
78
79    @Override
80    public boolean getPadding(Rect padding) {
81        final Rect r = mDrawableContainerState.getConstantPadding();
82        if (r != null) {
83            padding.set(r);
84            return true;
85        }
86        if (mCurrDrawable != null) {
87            return mCurrDrawable.getPadding(padding);
88        } else {
89            return super.getPadding(padding);
90        }
91    }
92
93    @Override
94    public void setAlpha(int alpha) {
95        if (mAlpha != alpha) {
96            mAlpha = alpha;
97            if (mCurrDrawable != null) {
98                if (mEnterAnimationEnd == 0) {
99                    mCurrDrawable.setAlpha(alpha);
100                } else {
101                    animate(false);
102                }
103            }
104        }
105    }
106
107    @Override
108    public void setDither(boolean dither) {
109        if (mDrawableContainerState.mDither != dither) {
110            mDrawableContainerState.mDither = dither;
111            if (mCurrDrawable != null) {
112                mCurrDrawable.setDither(mDrawableContainerState.mDither);
113            }
114        }
115    }
116
117    @Override
118    public void setColorFilter(ColorFilter cf) {
119        if (mColorFilter != cf) {
120            mColorFilter = cf;
121            if (mCurrDrawable != null) {
122                mCurrDrawable.setColorFilter(cf);
123            }
124        }
125    }
126
127    /**
128     * Change the global fade duration when a new drawable is entering
129     * the scene.
130     * @param ms The amount of time to fade in milliseconds.
131     */
132    public void setEnterFadeDuration(int ms) {
133        mDrawableContainerState.mEnterFadeDuration = ms;
134    }
135
136    /**
137     * Change the global fade duration when a new drawable is leaving
138     * the scene.
139     * @param ms The amount of time to fade in milliseconds.
140     */
141    public void setExitFadeDuration(int ms) {
142        mDrawableContainerState.mExitFadeDuration = ms;
143    }
144
145    @Override
146    protected void onBoundsChange(Rect bounds) {
147        if (mLastDrawable != null) {
148            mLastDrawable.setBounds(bounds);
149        }
150        if (mCurrDrawable != null) {
151            mCurrDrawable.setBounds(bounds);
152        }
153    }
154
155    @Override
156    public boolean isStateful() {
157        return mDrawableContainerState.isStateful();
158    }
159
160    @Override
161    public void jumpToCurrentState() {
162        boolean changed = false;
163        if (mLastDrawable != null) {
164            mLastDrawable.jumpToCurrentState();
165            mLastDrawable = null;
166            changed = true;
167        }
168        if (mCurrDrawable != null) {
169            mCurrDrawable.jumpToCurrentState();
170        }
171        if (mExitAnimationEnd != 0) {
172            mExitAnimationEnd = 0;
173            changed = true;
174        }
175        if (mEnterAnimationEnd != 0) {
176            mEnterAnimationEnd = 0;
177            changed = true;
178        }
179        if (changed) {
180            invalidateSelf();
181        }
182    }
183
184    @Override
185    protected boolean onStateChange(int[] state) {
186        if (mLastDrawable != null) {
187            return mLastDrawable.setState(state);
188        }
189        if (mCurrDrawable != null) {
190            return mCurrDrawable.setState(state);
191        }
192        return false;
193    }
194
195    @Override
196    protected boolean onLevelChange(int level) {
197        if (mLastDrawable != null) {
198            return mLastDrawable.setLevel(level);
199        }
200        if (mCurrDrawable != null) {
201            return mCurrDrawable.setLevel(level);
202        }
203        return false;
204    }
205
206    @Override
207    public int getIntrinsicWidth() {
208        if (mDrawableContainerState.isConstantSize()) {
209            return mDrawableContainerState.getConstantWidth();
210        }
211        return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1;
212    }
213
214    @Override
215    public int getIntrinsicHeight() {
216        if (mDrawableContainerState.isConstantSize()) {
217            return mDrawableContainerState.getConstantHeight();
218        }
219        return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1;
220    }
221
222    @Override
223    public int getMinimumWidth() {
224        if (mDrawableContainerState.isConstantSize()) {
225            return mDrawableContainerState.getConstantMinimumWidth();
226        }
227        return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0;
228    }
229
230    @Override
231    public int getMinimumHeight() {
232        if (mDrawableContainerState.isConstantSize()) {
233            return mDrawableContainerState.getConstantMinimumHeight();
234        }
235        return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0;
236    }
237
238    public void invalidateDrawable(Drawable who) {
239        if (who == mCurrDrawable && getCallback() != null) {
240            getCallback().invalidateDrawable(this);
241        }
242    }
243
244    public void scheduleDrawable(Drawable who, Runnable what, long when) {
245        if (who == mCurrDrawable && getCallback() != null) {
246            getCallback().scheduleDrawable(this, what, when);
247        }
248    }
249
250    public void unscheduleDrawable(Drawable who, Runnable what) {
251        if (who == mCurrDrawable && getCallback() != null) {
252            getCallback().unscheduleDrawable(this, what);
253        }
254    }
255
256    @Override
257    public boolean setVisible(boolean visible, boolean restart) {
258        boolean changed = super.setVisible(visible, restart);
259        if (mLastDrawable != null) {
260            mLastDrawable.setVisible(visible, restart);
261        }
262        if (mCurrDrawable != null) {
263            mCurrDrawable.setVisible(visible, restart);
264        }
265        return changed;
266    }
267
268    @Override
269    public int getOpacity() {
270        return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT :
271                mDrawableContainerState.getOpacity();
272    }
273
274    public boolean selectDrawable(int idx) {
275        if (idx == mCurIndex) {
276            return false;
277        }
278
279        final long now = SystemClock.uptimeMillis();
280
281        if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + idx
282                + ": exit=" + mDrawableContainerState.mExitFadeDuration
283                + " enter=" + mDrawableContainerState.mEnterFadeDuration);
284
285        if (mDrawableContainerState.mExitFadeDuration > 0) {
286            if (mLastDrawable != null) {
287                mLastDrawable.setVisible(false, false);
288            }
289            if (mCurrDrawable != null) {
290                mLastDrawable = mCurrDrawable;
291                mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
292            } else {
293                mLastDrawable = null;
294                mExitAnimationEnd = 0;
295            }
296        } else if (mCurrDrawable != null) {
297            mCurrDrawable.setVisible(false, false);
298        }
299
300        if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
301            Drawable d = mDrawableContainerState.mDrawables[idx];
302            mCurrDrawable = d;
303            mCurIndex = idx;
304            if (d != null) {
305                if (mDrawableContainerState.mEnterFadeDuration > 0) {
306                    mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
307                } else {
308                    d.setAlpha(mAlpha);
309                }
310                d.setVisible(isVisible(), true);
311                d.setDither(mDrawableContainerState.mDither);
312                d.setColorFilter(mColorFilter);
313                d.setState(getState());
314                d.setLevel(getLevel());
315                d.setBounds(getBounds());
316            }
317        } else {
318            mCurrDrawable = null;
319            mCurIndex = -1;
320        }
321
322        if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
323            if (mAnimationRunnable == null) {
324                mAnimationRunnable = new Runnable() {
325                    @Override public void run() {
326                        animate(true);
327                        invalidateSelf();
328                    }
329                };
330            } else {
331                unscheduleSelf(mAnimationRunnable);
332            }
333            // Compute first frame and schedule next animation.
334            animate(true);
335        }
336
337        invalidateSelf();
338
339        return true;
340    }
341
342    void animate(boolean schedule) {
343        final long now = SystemClock.uptimeMillis();
344        boolean animating = false;
345        if (mCurrDrawable != null) {
346            if (mEnterAnimationEnd != 0) {
347                if (mEnterAnimationEnd <= now) {
348                    mCurrDrawable.setAlpha(mAlpha);
349                    mEnterAnimationEnd = 0;
350                } else {
351                    int animAlpha = (int)((mEnterAnimationEnd-now)*255)
352                            / mDrawableContainerState.mEnterFadeDuration;
353                    if (DEBUG) android.util.Log.i(TAG, toString() + " cur alpha " + animAlpha);
354                    mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255);
355                    animating = true;
356                }
357            }
358        } else {
359            mEnterAnimationEnd = 0;
360        }
361        if (mLastDrawable != null) {
362            if (mExitAnimationEnd != 0) {
363                if (mExitAnimationEnd <= now) {
364                    mLastDrawable.setVisible(false, false);
365                    mLastDrawable = null;
366                    mExitAnimationEnd = 0;
367                } else {
368                    int animAlpha = (int)((mExitAnimationEnd-now)*255)
369                            / mDrawableContainerState.mExitFadeDuration;
370                    if (DEBUG) android.util.Log.i(TAG, toString() + " last alpha " + animAlpha);
371                    mLastDrawable.setAlpha((animAlpha*mAlpha)/255);
372                    animating = true;
373                }
374            }
375        } else {
376            mExitAnimationEnd = 0;
377        }
378
379        if (schedule && animating) {
380            scheduleSelf(mAnimationRunnable, now + 1000/60);
381        }
382    }
383
384    @Override
385    public Drawable getCurrent() {
386        return mCurrDrawable;
387    }
388
389    @Override
390    public ConstantState getConstantState() {
391        if (mDrawableContainerState.canConstantState()) {
392            mDrawableContainerState.mChangingConfigurations = super.getChangingConfigurations();
393            return mDrawableContainerState;
394        }
395        return null;
396    }
397
398    @Override
399    public Drawable mutate() {
400        if (!mMutated && super.mutate() == this) {
401            final int N = mDrawableContainerState.getChildCount();
402            final Drawable[] drawables = mDrawableContainerState.getChildren();
403            for (int i = 0; i < N; i++) {
404                if (drawables[i] != null) drawables[i].mutate();
405            }
406            mMutated = true;
407        }
408        return this;
409    }
410
411    /**
412     * A ConstantState that can contain several {@link Drawable}s.
413     *
414     * This class was made public to enable testing, and its visibility may change in a future
415     * release.
416     */
417    public abstract static class DrawableContainerState extends ConstantState {
418        final DrawableContainer mOwner;
419
420        int         mChangingConfigurations;
421        int         mChildrenChangingConfigurations;
422
423        Drawable[]  mDrawables;
424        int         mNumChildren;
425
426        boolean     mVariablePadding = false;
427        Rect        mConstantPadding = null;
428
429        boolean     mConstantSize = false;
430        boolean     mComputedConstantSize = false;
431        int         mConstantWidth;
432        int         mConstantHeight;
433        int         mConstantMinimumWidth;
434        int         mConstantMinimumHeight;
435
436        boolean     mHaveOpacity = false;
437        int         mOpacity;
438
439        boolean     mHaveStateful = false;
440        boolean     mStateful;
441
442        boolean     mCheckedConstantState;
443        boolean     mCanConstantState;
444
445        boolean     mPaddingChecked = false;
446
447        boolean     mDither = DEFAULT_DITHER;
448
449        int         mEnterFadeDuration;
450        int         mExitFadeDuration;
451
452        DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
453                Resources res) {
454            mOwner = owner;
455
456            if (orig != null) {
457                mChangingConfigurations = orig.mChangingConfigurations;
458                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
459
460                final Drawable[] origDr = orig.mDrawables;
461
462                mDrawables = new Drawable[origDr.length];
463                mNumChildren = orig.mNumChildren;
464
465                final int N = mNumChildren;
466                for (int i=0; i<N; i++) {
467                    if (res != null) {
468                        mDrawables[i] = origDr[i].getConstantState().newDrawable(res);
469                    } else {
470                        mDrawables[i] = origDr[i].getConstantState().newDrawable();
471                    }
472                    mDrawables[i].setCallback(owner);
473                }
474
475                mCheckedConstantState = mCanConstantState = true;
476                mVariablePadding = orig.mVariablePadding;
477                if (orig.mConstantPadding != null) {
478                    mConstantPadding = new Rect(orig.mConstantPadding);
479                }
480                mConstantSize = orig.mConstantSize;
481                mComputedConstantSize = orig.mComputedConstantSize;
482                mConstantWidth = orig.mConstantWidth;
483                mConstantHeight = orig.mConstantHeight;
484
485                mHaveOpacity = orig.mHaveOpacity;
486                mOpacity = orig.mOpacity;
487                mHaveStateful = orig.mHaveStateful;
488                mStateful = orig.mStateful;
489
490                mDither = orig.mDither;
491
492                mEnterFadeDuration = orig.mEnterFadeDuration;
493                mExitFadeDuration = orig.mExitFadeDuration;
494
495            } else {
496                mDrawables = new Drawable[10];
497                mNumChildren = 0;
498                mCheckedConstantState = mCanConstantState = false;
499            }
500        }
501
502        @Override
503        public int getChangingConfigurations() {
504            return mChangingConfigurations;
505        }
506
507        public final int addChild(Drawable dr) {
508            final int pos = mNumChildren;
509
510            if (pos >= mDrawables.length) {
511                growArray(pos, pos+10);
512            }
513
514            dr.setVisible(false, true);
515            dr.setCallback(mOwner);
516
517            mDrawables[pos] = dr;
518            mNumChildren++;
519            mChildrenChangingConfigurations |= dr.getChangingConfigurations();
520            mHaveOpacity = false;
521            mHaveStateful = false;
522
523            mConstantPadding = null;
524            mPaddingChecked = false;
525            mComputedConstantSize = false;
526
527            return pos;
528        }
529
530        public final int getChildCount() {
531            return mNumChildren;
532        }
533
534        public final Drawable[] getChildren() {
535            return mDrawables;
536        }
537
538        /** A boolean value indicating whether to use the maximum padding value of
539          * all frames in the set (false), or to use the padding value of the frame
540          * being shown (true). Default value is false.
541          */
542        public final void setVariablePadding(boolean variable) {
543            mVariablePadding = variable;
544        }
545
546        public final Rect getConstantPadding() {
547            if (mVariablePadding) {
548                return null;
549            }
550            if (mConstantPadding != null || mPaddingChecked) {
551                return mConstantPadding;
552            }
553
554            Rect r = null;
555            final Rect t = new Rect();
556            final int N = getChildCount();
557            final Drawable[] drawables = mDrawables;
558            for (int i = 0; i < N; i++) {
559                if (drawables[i].getPadding(t)) {
560                    if (r == null) r = new Rect(0, 0, 0, 0);
561                    if (t.left > r.left) r.left = t.left;
562                    if (t.top > r.top) r.top = t.top;
563                    if (t.right > r.right) r.right = t.right;
564                    if (t.bottom > r.bottom) r.bottom = t.bottom;
565                }
566            }
567            mPaddingChecked = true;
568            return (mConstantPadding = r);
569        }
570
571        public final void setConstantSize(boolean constant) {
572            mConstantSize = constant;
573        }
574
575        public final boolean isConstantSize() {
576            return mConstantSize;
577        }
578
579        public final int getConstantWidth() {
580            if (!mComputedConstantSize) {
581                computeConstantSize();
582            }
583
584            return mConstantWidth;
585        }
586
587        public final int getConstantHeight() {
588            if (!mComputedConstantSize) {
589                computeConstantSize();
590            }
591
592            return mConstantHeight;
593        }
594
595        public final int getConstantMinimumWidth() {
596            if (!mComputedConstantSize) {
597                computeConstantSize();
598            }
599
600            return mConstantMinimumWidth;
601        }
602
603        public final int getConstantMinimumHeight() {
604            if (!mComputedConstantSize) {
605                computeConstantSize();
606            }
607
608            return mConstantMinimumHeight;
609        }
610
611        protected void computeConstantSize() {
612            mComputedConstantSize = true;
613
614            final int N = getChildCount();
615            final Drawable[] drawables = mDrawables;
616            mConstantWidth = mConstantHeight = -1;
617            mConstantMinimumWidth = mConstantMinimumHeight = 0;
618            for (int i = 0; i < N; i++) {
619                Drawable dr = drawables[i];
620                int s = dr.getIntrinsicWidth();
621                if (s > mConstantWidth) mConstantWidth = s;
622                s = dr.getIntrinsicHeight();
623                if (s > mConstantHeight) mConstantHeight = s;
624                s = dr.getMinimumWidth();
625                if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
626                s = dr.getMinimumHeight();
627                if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
628            }
629        }
630
631        public final void setEnterFadeDuration(int duration) {
632            mEnterFadeDuration = duration;
633        }
634
635        public final int getEnterFadeDuration() {
636            return mEnterFadeDuration;
637        }
638
639        public final void setExitFadeDuration(int duration) {
640            mExitFadeDuration = duration;
641        }
642
643        public final int getExitFadeDuration() {
644            return mExitFadeDuration;
645        }
646
647        public final int getOpacity() {
648            if (mHaveOpacity) {
649                return mOpacity;
650            }
651
652            final int N = getChildCount();
653            final Drawable[] drawables = mDrawables;
654            int op = N > 0 ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
655            for (int i = 1; i < N; i++) {
656                op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
657            }
658            mOpacity = op;
659            mHaveOpacity = true;
660            return op;
661        }
662
663        public final boolean isStateful() {
664            if (mHaveStateful) {
665                return mStateful;
666            }
667
668            boolean stateful = false;
669            final int N = getChildCount();
670            for (int i = 0; i < N; i++) {
671                if (mDrawables[i].isStateful()) {
672                    stateful = true;
673                    break;
674                }
675            }
676
677            mStateful = stateful;
678            mHaveStateful = true;
679            return stateful;
680        }
681
682        public void growArray(int oldSize, int newSize) {
683            Drawable[] newDrawables = new Drawable[newSize];
684            System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
685            mDrawables = newDrawables;
686        }
687
688        public synchronized boolean canConstantState() {
689            if (!mCheckedConstantState) {
690                mCanConstantState = true;
691                final int N = mNumChildren;
692                for (int i=0; i<N; i++) {
693                    if (mDrawables[i].getConstantState() == null) {
694                        mCanConstantState = false;
695                        break;
696                    }
697                }
698                mCheckedConstantState = true;
699            }
700
701            return mCanConstantState;
702        }
703    }
704
705    protected void setConstantState(DrawableContainerState state)
706    {
707        mDrawableContainerState = state;
708    }
709}
710