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