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