DrawableContainer.java revision 27cf86939d918b2d6b32a0650a48b8a2afaa26b6
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.annotation.NonNull;
20import android.content.res.ColorStateList;
21import android.content.res.Resources;
22import android.content.res.Resources.Theme;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.graphics.ColorFilter;
26import android.graphics.Insets;
27import android.graphics.Outline;
28import android.graphics.PixelFormat;
29import android.graphics.Rect;
30import android.graphics.PorterDuff.Mode;
31import android.os.SystemClock;
32import android.util.LayoutDirection;
33import android.util.SparseArray;
34
35import java.util.Collection;
36
37/**
38 * A helper class that contains several {@link Drawable}s and selects which one to use.
39 *
40 * You can subclass it to create your own DrawableContainers or directly use one its child classes.
41 */
42public class DrawableContainer extends Drawable implements Drawable.Callback {
43    private static final boolean DEBUG = false;
44    private static final String TAG = "DrawableContainer";
45
46    /**
47     * To be proper, we should have a getter for dither (and alpha, etc.)
48     * so that proxy classes like this can save/restore their delegates'
49     * values, but we don't have getters. Since we do have setters
50     * (e.g. setDither), which this proxy forwards on, we have to have some
51     * default/initial setting.
52     *
53     * The initial setting for dither is now true, since it almost always seems
54     * to improve the quality at negligible cost.
55     */
56    private static final boolean DEFAULT_DITHER = true;
57    private DrawableContainerState mDrawableContainerState;
58    private Rect mHotspotBounds;
59    private Drawable mCurrDrawable;
60    private Drawable mLastDrawable;
61    private int mAlpha = 0xFF;
62
63    /** Whether setAlpha() has been called at least once. */
64    private boolean mHasAlpha;
65
66    private int mCurIndex = -1;
67    private int mLastIndex = -1;
68    private boolean mMutated;
69
70    // Animations.
71    private Runnable mAnimationRunnable;
72    private long mEnterAnimationEnd;
73    private long mExitAnimationEnd;
74
75    // overrides from Drawable
76
77    @Override
78    public void draw(Canvas canvas) {
79        if (mCurrDrawable != null) {
80            mCurrDrawable.draw(canvas);
81        }
82        if (mLastDrawable != null) {
83            mLastDrawable.draw(canvas);
84        }
85    }
86
87    @Override
88    public int getChangingConfigurations() {
89        return super.getChangingConfigurations()
90                | mDrawableContainerState.getChangingConfigurations();
91    }
92
93    private boolean needsMirroring() {
94        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
95    }
96
97    @Override
98    public boolean getPadding(Rect padding) {
99        final Rect r = mDrawableContainerState.getConstantPadding();
100        boolean result;
101        if (r != null) {
102            padding.set(r);
103            result = (r.left | r.top | r.bottom | r.right) != 0;
104        } else {
105            if (mCurrDrawable != null) {
106                result = mCurrDrawable.getPadding(padding);
107            } else {
108                result = super.getPadding(padding);
109            }
110        }
111        if (needsMirroring()) {
112            final int left = padding.left;
113            final int right = padding.right;
114            padding.left = right;
115            padding.right = left;
116        }
117        return result;
118    }
119
120    /**
121     * @hide
122     */
123    @Override
124    public Insets getOpticalInsets() {
125        if (mCurrDrawable != null) {
126            return mCurrDrawable.getOpticalInsets();
127        }
128        return Insets.NONE;
129    }
130
131    @Override
132    public void getOutline(@NonNull Outline outline) {
133        if (mCurrDrawable != null) {
134            mCurrDrawable.getOutline(outline);
135        }
136    }
137
138    @Override
139    public void setAlpha(int alpha) {
140        if (!mHasAlpha || mAlpha != alpha) {
141            mHasAlpha = true;
142            mAlpha = alpha;
143            if (mCurrDrawable != null) {
144                if (mEnterAnimationEnd == 0) {
145                    mCurrDrawable.mutate().setAlpha(alpha);
146                } else {
147                    animate(false);
148                }
149            }
150        }
151    }
152
153    @Override
154    public int getAlpha() {
155        return mAlpha;
156    }
157
158    @Override
159    public void setDither(boolean dither) {
160        if (mDrawableContainerState.mDither != dither) {
161            mDrawableContainerState.mDither = dither;
162            if (mCurrDrawable != null) {
163                mCurrDrawable.mutate().setDither(mDrawableContainerState.mDither);
164            }
165        }
166    }
167
168    @Override
169    public boolean getDither() {
170        return mDrawableContainerState.mDither;
171    }
172
173    @Override
174    public void setColorFilter(ColorFilter colorFilter) {
175        mDrawableContainerState.mHasColorFilter = true;
176
177        if (mDrawableContainerState.mColorFilter != colorFilter) {
178            mDrawableContainerState.mColorFilter = colorFilter;
179
180            if (mCurrDrawable != null) {
181                mCurrDrawable.mutate().setColorFilter(colorFilter);
182            }
183        }
184    }
185
186    @Override
187    public void setTintList(ColorStateList tint) {
188        mDrawableContainerState.mHasTintList = true;
189
190        if (mDrawableContainerState.mTintList != tint) {
191            mDrawableContainerState.mTintList = tint;
192
193            if (mCurrDrawable != null) {
194                mCurrDrawable.mutate().setTintList(tint);
195            }
196        }
197    }
198
199    @Override
200    public void setTintMode(Mode tintMode) {
201        mDrawableContainerState.mHasTintMode = true;
202
203        if (mDrawableContainerState.mTintMode != tintMode) {
204            mDrawableContainerState.mTintMode = tintMode;
205
206            if (mCurrDrawable != null) {
207                mCurrDrawable.mutate().setTintMode(tintMode);
208            }
209        }
210    }
211
212    /**
213     * Change the global fade duration when a new drawable is entering
214     * the scene.
215     * @param ms The amount of time to fade in milliseconds.
216     */
217    public void setEnterFadeDuration(int ms) {
218        mDrawableContainerState.mEnterFadeDuration = ms;
219    }
220
221    /**
222     * Change the global fade duration when a new drawable is leaving
223     * the scene.
224     * @param ms The amount of time to fade in milliseconds.
225     */
226    public void setExitFadeDuration(int ms) {
227        mDrawableContainerState.mExitFadeDuration = ms;
228    }
229
230    @Override
231    protected void onBoundsChange(Rect bounds) {
232        if (mLastDrawable != null) {
233            mLastDrawable.setBounds(bounds);
234        }
235        if (mCurrDrawable != null) {
236            mCurrDrawable.setBounds(bounds);
237        }
238    }
239
240    @Override
241    public boolean isStateful() {
242        return mDrawableContainerState.isStateful();
243    }
244
245    @Override
246    public void setAutoMirrored(boolean mirrored) {
247        if (mDrawableContainerState.mAutoMirrored != mirrored) {
248            mDrawableContainerState.mAutoMirrored = mirrored;
249            if (mCurrDrawable != null) {
250                mCurrDrawable.mutate().setAutoMirrored(mDrawableContainerState.mAutoMirrored);
251            }
252        }
253    }
254
255    @Override
256    public boolean isAutoMirrored() {
257        return mDrawableContainerState.mAutoMirrored;
258    }
259
260    @Override
261    public void jumpToCurrentState() {
262        boolean changed = false;
263        if (mLastDrawable != null) {
264            mLastDrawable.jumpToCurrentState();
265            mLastDrawable = null;
266            mLastIndex = -1;
267            changed = true;
268        }
269        if (mCurrDrawable != null) {
270            mCurrDrawable.jumpToCurrentState();
271            if (mHasAlpha) {
272                mCurrDrawable.mutate().setAlpha(mAlpha);
273            }
274        }
275        if (mExitAnimationEnd != 0) {
276            mExitAnimationEnd = 0;
277            changed = true;
278        }
279        if (mEnterAnimationEnd != 0) {
280            mEnterAnimationEnd = 0;
281            changed = true;
282        }
283        if (changed) {
284            invalidateSelf();
285        }
286    }
287
288    @Override
289    public void setHotspot(float x, float y) {
290        if (mCurrDrawable != null) {
291            mCurrDrawable.setHotspot(x, y);
292        }
293    }
294
295    @Override
296    public void setHotspotBounds(int left, int top, int right, int bottom) {
297        if (mHotspotBounds == null) {
298            mHotspotBounds = new Rect(left, top, right, bottom);
299        } else {
300            mHotspotBounds.set(left, top, right, bottom);
301        }
302
303        if (mCurrDrawable != null) {
304            mCurrDrawable.setHotspotBounds(left, top, right, bottom);
305        }
306    }
307
308    @Override
309    public void getHotspotBounds(Rect outRect) {
310        if (mHotspotBounds != null) {
311            outRect.set(mHotspotBounds);
312        } else {
313            super.getHotspotBounds(outRect);
314        }
315    }
316
317    @Override
318    protected boolean onStateChange(int[] state) {
319        if (mLastDrawable != null) {
320            return mLastDrawable.setState(state);
321        }
322        if (mCurrDrawable != null) {
323            return mCurrDrawable.setState(state);
324        }
325        return false;
326    }
327
328    @Override
329    protected boolean onLevelChange(int level) {
330        if (mLastDrawable != null) {
331            return mLastDrawable.setLevel(level);
332        }
333        if (mCurrDrawable != null) {
334            return mCurrDrawable.setLevel(level);
335        }
336        return false;
337    }
338
339    @Override
340    public boolean onLayoutDirectionChange(int layoutDirection) {
341        // Let the container handle setting its own layout direction. Otherwise,
342        // we're accessing potentially unused states.
343        return mDrawableContainerState.setLayoutDirection(layoutDirection, getCurrentIndex());
344    }
345
346    @Override
347    public int getIntrinsicWidth() {
348        if (mDrawableContainerState.isConstantSize()) {
349            return mDrawableContainerState.getConstantWidth();
350        }
351        return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1;
352    }
353
354    @Override
355    public int getIntrinsicHeight() {
356        if (mDrawableContainerState.isConstantSize()) {
357            return mDrawableContainerState.getConstantHeight();
358        }
359        return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1;
360    }
361
362    @Override
363    public int getMinimumWidth() {
364        if (mDrawableContainerState.isConstantSize()) {
365            return mDrawableContainerState.getConstantMinimumWidth();
366        }
367        return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0;
368    }
369
370    @Override
371    public int getMinimumHeight() {
372        if (mDrawableContainerState.isConstantSize()) {
373            return mDrawableContainerState.getConstantMinimumHeight();
374        }
375        return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0;
376    }
377
378    @Override
379    public void invalidateDrawable(Drawable who) {
380        if (who == mCurrDrawable && getCallback() != null) {
381            getCallback().invalidateDrawable(this);
382        }
383    }
384
385    @Override
386    public void scheduleDrawable(Drawable who, Runnable what, long when) {
387        if (who == mCurrDrawable && getCallback() != null) {
388            getCallback().scheduleDrawable(this, what, when);
389        }
390    }
391
392    @Override
393    public void unscheduleDrawable(Drawable who, Runnable what) {
394        if (who == mCurrDrawable && getCallback() != null) {
395            getCallback().unscheduleDrawable(this, what);
396        }
397    }
398
399    @Override
400    public boolean setVisible(boolean visible, boolean restart) {
401        boolean changed = super.setVisible(visible, restart);
402        if (mLastDrawable != null) {
403            mLastDrawable.setVisible(visible, restart);
404        }
405        if (mCurrDrawable != null) {
406            mCurrDrawable.setVisible(visible, restart);
407        }
408        return changed;
409    }
410
411    @Override
412    public int getOpacity() {
413        return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT :
414                mDrawableContainerState.getOpacity();
415    }
416
417    /** @hide */
418    public void setCurrentIndex(int index) {
419        selectDrawable(index);
420    }
421
422    /** @hide */
423    public int getCurrentIndex() {
424        return mCurIndex;
425    }
426
427    public boolean selectDrawable(int idx) {
428        if (idx == mCurIndex) {
429            return false;
430        }
431
432        final long now = SystemClock.uptimeMillis();
433
434        if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + idx
435                + ": exit=" + mDrawableContainerState.mExitFadeDuration
436                + " enter=" + mDrawableContainerState.mEnterFadeDuration);
437
438        if (mDrawableContainerState.mExitFadeDuration > 0) {
439            if (mLastDrawable != null) {
440                mLastDrawable.setVisible(false, false);
441            }
442            if (mCurrDrawable != null) {
443                mLastDrawable = mCurrDrawable;
444                mLastIndex = mCurIndex;
445                mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
446            } else {
447                mLastDrawable = null;
448                mLastIndex = -1;
449                mExitAnimationEnd = 0;
450            }
451        } else if (mCurrDrawable != null) {
452            mCurrDrawable.setVisible(false, false);
453        }
454
455        if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
456            final Drawable d = mDrawableContainerState.getChild(idx);
457            mCurrDrawable = d;
458            mCurIndex = idx;
459            if (d != null) {
460                if (mDrawableContainerState.mEnterFadeDuration > 0) {
461                    mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
462                }
463                initializeDrawableForDisplay(d);
464            }
465        } else {
466            mCurrDrawable = null;
467            mCurIndex = -1;
468        }
469
470        if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
471            if (mAnimationRunnable == null) {
472                mAnimationRunnable = new Runnable() {
473                    @Override public void run() {
474                        animate(true);
475                        invalidateSelf();
476                    }
477                };
478            } else {
479                unscheduleSelf(mAnimationRunnable);
480            }
481            // Compute first frame and schedule next animation.
482            animate(true);
483        }
484
485        invalidateSelf();
486
487        return true;
488    }
489
490    /**
491     * Initializes a drawable for display in this container.
492     *
493     * @param d The drawable to initialize.
494     */
495    private void initializeDrawableForDisplay(Drawable d) {
496        d.mutate();
497
498        if (mDrawableContainerState.mEnterFadeDuration <= 0 && mHasAlpha) {
499            d.setAlpha(mAlpha);
500        }
501
502        if (mDrawableContainerState.mHasColorFilter) {
503            // Color filter always overrides tint.
504            d.setColorFilter(mDrawableContainerState.mColorFilter);
505        } else {
506            if (mDrawableContainerState.mHasTintList) {
507                d.setTintList(mDrawableContainerState.mTintList);
508            }
509            if (mDrawableContainerState.mHasTintMode) {
510                d.setTintMode(mDrawableContainerState.mTintMode);
511            }
512        }
513
514        d.setVisible(isVisible(), true);
515        d.setDither(mDrawableContainerState.mDither);
516        d.setState(getState());
517        d.setLevel(getLevel());
518        d.setBounds(getBounds());
519        d.setLayoutDirection(getLayoutDirection());
520        d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
521
522        final Rect hotspotBounds = mHotspotBounds;
523        if (hotspotBounds != null) {
524            d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top,
525                    hotspotBounds.right, hotspotBounds.bottom);
526        }
527    }
528
529    void animate(boolean schedule) {
530        mHasAlpha = true;
531
532        final long now = SystemClock.uptimeMillis();
533        boolean animating = false;
534        if (mCurrDrawable != null) {
535            if (mEnterAnimationEnd != 0) {
536                if (mEnterAnimationEnd <= now) {
537                    mCurrDrawable.mutate().setAlpha(mAlpha);
538                    mEnterAnimationEnd = 0;
539                } else {
540                    int animAlpha = (int)((mEnterAnimationEnd-now)*255)
541                            / mDrawableContainerState.mEnterFadeDuration;
542                    if (DEBUG) android.util.Log.i(TAG, toString() + " cur alpha " + animAlpha);
543                    mCurrDrawable.mutate().setAlpha(((255-animAlpha)*mAlpha)/255);
544                    animating = true;
545                }
546            }
547        } else {
548            mEnterAnimationEnd = 0;
549        }
550        if (mLastDrawable != null) {
551            if (mExitAnimationEnd != 0) {
552                if (mExitAnimationEnd <= now) {
553                    mLastDrawable.setVisible(false, false);
554                    mLastDrawable = null;
555                    mLastIndex = -1;
556                    mExitAnimationEnd = 0;
557                } else {
558                    int animAlpha = (int)((mExitAnimationEnd-now)*255)
559                            / mDrawableContainerState.mExitFadeDuration;
560                    if (DEBUG) android.util.Log.i(TAG, toString() + " last alpha " + animAlpha);
561                    mLastDrawable.mutate().setAlpha((animAlpha*mAlpha)/255);
562                    animating = true;
563                }
564            }
565        } else {
566            mExitAnimationEnd = 0;
567        }
568
569        if (schedule && animating) {
570            scheduleSelf(mAnimationRunnable, now + 1000 / 60);
571        }
572    }
573
574    @Override
575    public Drawable getCurrent() {
576        return mCurrDrawable;
577    }
578
579    @Override
580    public void applyTheme(Theme theme) {
581        mDrawableContainerState.applyTheme(theme);
582    }
583
584    @Override
585    public boolean canApplyTheme() {
586        return mDrawableContainerState.canApplyTheme();
587    }
588
589    @Override
590    public ConstantState getConstantState() {
591        if (mDrawableContainerState.canConstantState()) {
592            mDrawableContainerState.mChangingConfigurations = getChangingConfigurations();
593            return mDrawableContainerState;
594        }
595        return null;
596    }
597
598    @Override
599    public Drawable mutate() {
600        if (!mMutated && super.mutate() == this) {
601            final DrawableContainerState clone = cloneConstantState();
602            clone.mutate();
603            setConstantState(clone);
604            mMutated = true;
605        }
606        return this;
607    }
608
609    /**
610     * Returns a shallow copy of the container's constant state to be used as
611     * the base state for {@link #mutate()}.
612     *
613     * @return a shallow copy of the constant state
614     */
615    DrawableContainerState cloneConstantState() {
616        return mDrawableContainerState;
617    }
618
619    /**
620     * @hide
621     */
622    public void clearMutated() {
623        super.clearMutated();
624        mDrawableContainerState.clearMutated();
625        mMutated = false;
626    }
627
628    /**
629     * A ConstantState that can contain several {@link Drawable}s.
630     *
631     * This class was made public to enable testing, and its visibility may change in a future
632     * release.
633     */
634    public abstract static class DrawableContainerState extends ConstantState {
635        final DrawableContainer mOwner;
636        final Resources mRes;
637
638        SparseArray<ConstantStateFuture> mDrawableFutures;
639
640        int mChangingConfigurations;
641        int mChildrenChangingConfigurations;
642
643        Drawable[] mDrawables;
644        int mNumChildren;
645
646        boolean mVariablePadding = false;
647        boolean mPaddingChecked;
648        Rect mConstantPadding;
649
650        boolean mConstantSize = false;
651        boolean mComputedConstantSize;
652        int mConstantWidth;
653        int mConstantHeight;
654        int mConstantMinimumWidth;
655        int mConstantMinimumHeight;
656
657        boolean mCheckedOpacity;
658        int mOpacity;
659
660        boolean mCheckedStateful;
661        boolean mStateful;
662
663        boolean mCheckedConstantState;
664        boolean mCanConstantState;
665
666        boolean mDither = DEFAULT_DITHER;
667
668        boolean mMutated;
669        int mLayoutDirection;
670
671        int mEnterFadeDuration = 0;
672        int mExitFadeDuration = 0;
673
674        boolean mAutoMirrored;
675
676        ColorFilter mColorFilter;
677        boolean mHasColorFilter;
678
679        ColorStateList mTintList;
680        Mode mTintMode;
681        boolean mHasTintList;
682        boolean mHasTintMode;
683
684        DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
685                Resources res) {
686            mOwner = owner;
687            mRes = res != null ? res : orig != null ? orig.mRes : null;
688
689            if (orig != null) {
690                mChangingConfigurations = orig.mChangingConfigurations;
691                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
692
693                mCheckedConstantState = true;
694                mCanConstantState = true;
695
696                mVariablePadding = orig.mVariablePadding;
697                mConstantSize = orig.mConstantSize;
698                mDither = orig.mDither;
699                mMutated = orig.mMutated;
700                mLayoutDirection = orig.mLayoutDirection;
701                mEnterFadeDuration = orig.mEnterFadeDuration;
702                mExitFadeDuration = orig.mExitFadeDuration;
703                mAutoMirrored = orig.mAutoMirrored;
704                mColorFilter = orig.mColorFilter;
705                mHasColorFilter = orig.mHasColorFilter;
706                mTintList = orig.mTintList;
707                mTintMode = orig.mTintMode;
708                mHasTintList = orig.mHasTintList;
709                mHasTintMode = orig.mHasTintMode;
710
711                // Cloning the following values may require creating futures.
712                mConstantPadding = orig.getConstantPadding();
713                mPaddingChecked = true;
714
715                mConstantWidth = orig.getConstantWidth();
716                mConstantHeight = orig.getConstantHeight();
717                mConstantMinimumWidth = orig.getConstantMinimumWidth();
718                mConstantMinimumHeight = orig.getConstantMinimumHeight();
719                mComputedConstantSize = true;
720
721                mOpacity = orig.getOpacity();
722                mCheckedOpacity = true;
723
724                mStateful = orig.isStateful();
725                mCheckedStateful = true;
726
727                // Postpone cloning children and futures until we're absolutely
728                // sure that we're done computing values for the original state.
729                final Drawable[] origDr = orig.mDrawables;
730                mDrawables = new Drawable[origDr.length];
731                mNumChildren = orig.mNumChildren;
732
733                final SparseArray<ConstantStateFuture> origDf = orig.mDrawableFutures;
734                if (origDf != null) {
735                    mDrawableFutures = origDf.clone();
736                } else {
737                    mDrawableFutures = new SparseArray<ConstantStateFuture>(mNumChildren);
738                }
739
740                // Create futures for drawables with constant states. If a
741                // drawable doesn't have a constant state, then we can't clone
742                // it and we'll have to reference the original.
743                final int N = mNumChildren;
744                for (int i = 0; i < N; i++) {
745                    if (origDr[i] != null) {
746                        if (origDr[i].getConstantState() != null) {
747                            mDrawableFutures.put(i, new ConstantStateFuture(origDr[i]));
748                        } else {
749                            mDrawables[i] = origDr[i];
750                        }
751                    }
752                }
753            } else {
754                mDrawables = new Drawable[10];
755                mNumChildren = 0;
756            }
757        }
758
759        @Override
760        public int getChangingConfigurations() {
761            return mChangingConfigurations | mChildrenChangingConfigurations;
762        }
763
764        public final int addChild(Drawable dr) {
765            final int pos = mNumChildren;
766
767            if (pos >= mDrawables.length) {
768                growArray(pos, pos+10);
769            }
770
771            dr.setVisible(false, true);
772            dr.setCallback(mOwner);
773
774            mDrawables[pos] = dr;
775            mNumChildren++;
776            mChildrenChangingConfigurations |= dr.getChangingConfigurations();
777            mCheckedStateful = false;
778            mCheckedOpacity = false;
779
780            mConstantPadding = null;
781            mPaddingChecked = false;
782            mComputedConstantSize = false;
783
784            return pos;
785        }
786
787        final int getCapacity() {
788            return mDrawables.length;
789        }
790
791        private final void createAllFutures() {
792            if (mDrawableFutures != null) {
793                final int futureCount = mDrawableFutures.size();
794                for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) {
795                    final int index = mDrawableFutures.keyAt(keyIndex);
796                    mDrawables[index] = mDrawableFutures.valueAt(keyIndex).get(this);
797                }
798
799                mDrawableFutures = null;
800            }
801        }
802
803        public final int getChildCount() {
804            return mNumChildren;
805        }
806
807        /*
808         * @deprecated Use {@link #getChild} instead.
809         */
810        public final Drawable[] getChildren() {
811            // Create all futures for backwards compatibility.
812            createAllFutures();
813
814            return mDrawables;
815        }
816
817        public final Drawable getChild(int index) {
818            final Drawable result = mDrawables[index];
819            if (result != null) {
820                return result;
821            }
822
823            // Prepare future drawable if necessary.
824            if (mDrawableFutures != null) {
825                final int keyIndex = mDrawableFutures.indexOfKey(index);
826                if (keyIndex >= 0) {
827                    final Drawable prepared = mDrawableFutures.valueAt(keyIndex).get(this);
828                    mDrawables[index] = prepared;
829                    mDrawableFutures.removeAt(keyIndex);
830                    return prepared;
831                }
832            }
833
834            return null;
835        }
836
837        final boolean setLayoutDirection(int layoutDirection, int currentIndex) {
838            boolean changed = false;
839
840            // No need to call createAllFutures, since future drawables will
841            // change layout direction when they are prepared.
842            final int N = mNumChildren;
843            final Drawable[] drawables = mDrawables;
844            for (int i = 0; i < N; i++) {
845                if (drawables[i] != null) {
846                    final boolean childChanged = drawables[i].setLayoutDirection(layoutDirection);
847                    if (i == currentIndex) {
848                        changed = childChanged;
849                    }
850                }
851            }
852
853            mLayoutDirection = layoutDirection;
854
855            return changed;
856        }
857
858        final void applyTheme(Theme theme) {
859            if (theme != null) {
860                createAllFutures();
861
862                final int N = mNumChildren;
863                final Drawable[] drawables = mDrawables;
864                for (int i = 0; i < N; i++) {
865                    if (drawables[i] != null && drawables[i].canApplyTheme()) {
866                        drawables[i].applyTheme(theme);
867
868                        // Update cached mask of child changing configurations.
869                        mChildrenChangingConfigurations |= drawables[i].getChangingConfigurations();
870                    }
871                }
872            }
873        }
874
875        @Override
876        public boolean canApplyTheme() {
877            final int N = mNumChildren;
878            final Drawable[] drawables = mDrawables;
879            for (int i = 0; i < N; i++) {
880                final Drawable d = drawables[i];
881                if (d != null) {
882                    if (d.canApplyTheme()) {
883                        return true;
884                    }
885                } else {
886                    final ConstantStateFuture future = mDrawableFutures.get(i);
887                    if (future != null && future.canApplyTheme()) {
888                        return true;
889                    }
890                }
891            }
892
893            return false;
894        }
895
896        private void mutate() {
897            // No need to call createAllFutures, since future drawables will
898            // mutate when they are prepared.
899            final int N = mNumChildren;
900            final Drawable[] drawables = mDrawables;
901            for (int i = 0; i < N; i++) {
902                if (drawables[i] != null) {
903                    drawables[i].mutate();
904                }
905            }
906
907            mMutated = true;
908        }
909
910        final void clearMutated() {
911            final int N = mNumChildren;
912            final Drawable[] drawables = mDrawables;
913            for (int i = 0; i < N; i++) {
914                if (drawables[i] != null) {
915                    drawables[i].clearMutated();
916                }
917            }
918
919            mMutated = false;
920        }
921
922        /**
923         * A boolean value indicating whether to use the maximum padding value
924         * of all frames in the set (false), or to use the padding value of the
925         * frame being shown (true). Default value is false.
926         */
927        public final void setVariablePadding(boolean variable) {
928            mVariablePadding = variable;
929        }
930
931        public final Rect getConstantPadding() {
932            if (mVariablePadding) {
933                return null;
934            }
935
936            if ((mConstantPadding != null) || mPaddingChecked) {
937                return mConstantPadding;
938            }
939
940            createAllFutures();
941
942            Rect r = null;
943            final Rect t = new Rect();
944            final int N = mNumChildren;
945            final Drawable[] drawables = mDrawables;
946            for (int i = 0; i < N; i++) {
947                if (drawables[i].getPadding(t)) {
948                    if (r == null) r = new Rect(0, 0, 0, 0);
949                    if (t.left > r.left) r.left = t.left;
950                    if (t.top > r.top) r.top = t.top;
951                    if (t.right > r.right) r.right = t.right;
952                    if (t.bottom > r.bottom) r.bottom = t.bottom;
953                }
954            }
955
956            mPaddingChecked = true;
957            return (mConstantPadding = r);
958        }
959
960        public final void setConstantSize(boolean constant) {
961            mConstantSize = constant;
962        }
963
964        public final boolean isConstantSize() {
965            return mConstantSize;
966        }
967
968        public final int getConstantWidth() {
969            if (!mComputedConstantSize) {
970                computeConstantSize();
971            }
972
973            return mConstantWidth;
974        }
975
976        public final int getConstantHeight() {
977            if (!mComputedConstantSize) {
978                computeConstantSize();
979            }
980
981            return mConstantHeight;
982        }
983
984        public final int getConstantMinimumWidth() {
985            if (!mComputedConstantSize) {
986                computeConstantSize();
987            }
988
989            return mConstantMinimumWidth;
990        }
991
992        public final int getConstantMinimumHeight() {
993            if (!mComputedConstantSize) {
994                computeConstantSize();
995            }
996
997            return mConstantMinimumHeight;
998        }
999
1000        protected void computeConstantSize() {
1001            mComputedConstantSize = true;
1002
1003            createAllFutures();
1004
1005            final int N = mNumChildren;
1006            final Drawable[] drawables = mDrawables;
1007            mConstantWidth = mConstantHeight = -1;
1008            mConstantMinimumWidth = mConstantMinimumHeight = 0;
1009            for (int i = 0; i < N; i++) {
1010                final Drawable dr = drawables[i];
1011                int s = dr.getIntrinsicWidth();
1012                if (s > mConstantWidth) mConstantWidth = s;
1013                s = dr.getIntrinsicHeight();
1014                if (s > mConstantHeight) mConstantHeight = s;
1015                s = dr.getMinimumWidth();
1016                if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
1017                s = dr.getMinimumHeight();
1018                if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
1019            }
1020        }
1021
1022        public final void setEnterFadeDuration(int duration) {
1023            mEnterFadeDuration = duration;
1024        }
1025
1026        public final int getEnterFadeDuration() {
1027            return mEnterFadeDuration;
1028        }
1029
1030        public final void setExitFadeDuration(int duration) {
1031            mExitFadeDuration = duration;
1032        }
1033
1034        public final int getExitFadeDuration() {
1035            return mExitFadeDuration;
1036        }
1037
1038        public final int getOpacity() {
1039            if (mCheckedOpacity) {
1040                return mOpacity;
1041            }
1042
1043            createAllFutures();
1044
1045            mCheckedOpacity = true;
1046
1047            final int N = mNumChildren;
1048            final Drawable[] drawables = mDrawables;
1049            int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
1050            for (int i = 1; i < N; i++) {
1051                op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
1052            }
1053
1054            mOpacity = op;
1055            return op;
1056        }
1057
1058        public final boolean isStateful() {
1059            if (mCheckedStateful) {
1060                return mStateful;
1061            }
1062
1063            createAllFutures();
1064
1065            mCheckedStateful = true;
1066
1067            final int N = mNumChildren;
1068            final Drawable[] drawables = mDrawables;
1069            for (int i = 0; i < N; i++) {
1070                if (drawables[i].isStateful()) {
1071                    mStateful = true;
1072                    return true;
1073                }
1074            }
1075
1076            mStateful = false;
1077            return false;
1078        }
1079
1080        public void growArray(int oldSize, int newSize) {
1081            Drawable[] newDrawables = new Drawable[newSize];
1082            System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
1083            mDrawables = newDrawables;
1084        }
1085
1086        public synchronized boolean canConstantState() {
1087            if (mCheckedConstantState) {
1088                return mCanConstantState;
1089            }
1090
1091            createAllFutures();
1092
1093            mCheckedConstantState = true;
1094
1095            final int N = mNumChildren;
1096            final Drawable[] drawables = mDrawables;
1097            for (int i = 0; i < N; i++) {
1098                if (drawables[i].getConstantState() == null) {
1099                    mCanConstantState = false;
1100                    return false;
1101                }
1102            }
1103
1104            mCanConstantState = true;
1105            return true;
1106        }
1107
1108        /** @hide */
1109        @Override
1110        public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
1111            final int N = mNumChildren;
1112            int pixelCount = 0;
1113            for (int i = 0; i < N; i++) {
1114                final ConstantState state = getChild(i).getConstantState();
1115                if (state != null) {
1116                    pixelCount += state.addAtlasableBitmaps(atlasList);
1117                }
1118            }
1119            return pixelCount;
1120        }
1121
1122        /**
1123         * Class capable of cloning a Drawable from another Drawable's
1124         * ConstantState.
1125         */
1126        private static class ConstantStateFuture {
1127            private final ConstantState mConstantState;
1128
1129            private ConstantStateFuture(Drawable source) {
1130                mConstantState = source.getConstantState();
1131            }
1132
1133            /**
1134             * Obtains and prepares the Drawable represented by this future.
1135             *
1136             * @param state the container into which this future will be placed
1137             * @return a prepared Drawable
1138             */
1139            public Drawable get(DrawableContainerState state) {
1140                final Drawable result;
1141                if (state.mRes == null) {
1142                    result = mConstantState.newDrawable();
1143                } else {
1144                    result = mConstantState.newDrawable(state.mRes);
1145                }
1146                result.setLayoutDirection(state.mLayoutDirection);
1147                result.setCallback(state.mOwner);
1148
1149                if (state.mMutated) {
1150                    result.mutate();
1151                }
1152
1153                return result;
1154            }
1155
1156            /**
1157             * Whether the constant state wrapped by this future can apply a
1158             * theme.
1159             */
1160            public boolean canApplyTheme() {
1161                return mConstantState.canApplyTheme();
1162            }
1163        }
1164    }
1165
1166    protected void setConstantState(DrawableContainerState state) {
1167        mDrawableContainerState = state;
1168
1169        // The locally cached drawables may have changed.
1170        if (mCurIndex >= 0) {
1171            mCurrDrawable = state.getChild(mCurIndex);
1172            if (mCurrDrawable != null) {
1173                initializeDrawableForDisplay(mCurrDrawable);
1174            }
1175        }
1176
1177        // Clear out the last drawable. We don't have enough information to
1178        // propagate local state from the past.
1179        mLastIndex = -1;
1180        mLastDrawable = null;
1181    }
1182}
1183