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