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