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