DrawableContainer.java revision 9b4bdeff6dea5a24a3085a17d2dde9003642af79
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.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.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.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.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.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.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.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        if (mDrawableContainerState.mEnterFadeDuration <= 0 && mHasAlpha) {
503            d.setAlpha(mAlpha);
504        }
505
506        if (mDrawableContainerState.mHasColorFilter) {
507            // Color filter always overrides tint.
508            d.setColorFilter(mDrawableContainerState.mColorFilter);
509        } else {
510            if (mDrawableContainerState.mHasTintList) {
511                d.setTintList(mDrawableContainerState.mTintList);
512            }
513            if (mDrawableContainerState.mHasTintMode) {
514                d.setTintMode(mDrawableContainerState.mTintMode);
515            }
516        }
517
518        d.setVisible(isVisible(), true);
519        d.setDither(mDrawableContainerState.mDither);
520        d.setState(getState());
521        d.setLevel(getLevel());
522        d.setBounds(getBounds());
523        d.setLayoutDirection(getLayoutDirection());
524        d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
525
526        final Rect hotspotBounds = mHotspotBounds;
527        if (hotspotBounds != null) {
528            d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top,
529                    hotspotBounds.right, hotspotBounds.bottom);
530        }
531    }
532
533    void animate(boolean schedule) {
534        mHasAlpha = true;
535
536        final long now = SystemClock.uptimeMillis();
537        boolean animating = false;
538        if (mCurrDrawable != null) {
539            if (mEnterAnimationEnd != 0) {
540                if (mEnterAnimationEnd <= now) {
541                    mCurrDrawable.setAlpha(mAlpha);
542                    mEnterAnimationEnd = 0;
543                } else {
544                    int animAlpha = (int)((mEnterAnimationEnd-now)*255)
545                            / mDrawableContainerState.mEnterFadeDuration;
546                    mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255);
547                    animating = true;
548                }
549            }
550        } else {
551            mEnterAnimationEnd = 0;
552        }
553        if (mLastDrawable != null) {
554            if (mExitAnimationEnd != 0) {
555                if (mExitAnimationEnd <= now) {
556                    mLastDrawable.setVisible(false, false);
557                    mLastDrawable = null;
558                    mLastIndex = -1;
559                    mExitAnimationEnd = 0;
560                } else {
561                    int animAlpha = (int)((mExitAnimationEnd-now)*255)
562                            / mDrawableContainerState.mExitFadeDuration;
563                    mLastDrawable.setAlpha((animAlpha*mAlpha)/255);
564                    animating = true;
565                }
566            }
567        } else {
568            mExitAnimationEnd = 0;
569        }
570
571        if (schedule && animating) {
572            scheduleSelf(mAnimationRunnable, now + 1000 / 60);
573        }
574    }
575
576    @Override
577    public Drawable getCurrent() {
578        return mCurrDrawable;
579    }
580
581    /**
582     * Updates the source density based on the resources used to inflate
583     * density-dependent values. Implementing classes should call this method
584     * during inflation.
585     *
586     * @param res the resources used to inflate density-dependent values
587     */
588    final void updateDensity(Resources res) {
589        mDrawableContainerState.updateDensity(res);
590    }
591
592    @Override
593    public void applyTheme(Theme theme) {
594        mDrawableContainerState.applyTheme(theme);
595    }
596
597    @Override
598    public boolean canApplyTheme() {
599        return mDrawableContainerState.canApplyTheme();
600    }
601
602    @Override
603    public ConstantState getConstantState() {
604        if (mDrawableContainerState.canConstantState()) {
605            mDrawableContainerState.mChangingConfigurations = getChangingConfigurations();
606            return mDrawableContainerState;
607        }
608        return null;
609    }
610
611    @Override
612    public Drawable mutate() {
613        if (!mMutated && super.mutate() == this) {
614            final DrawableContainerState clone = cloneConstantState();
615            clone.mutate();
616            setConstantState(clone);
617            mMutated = true;
618        }
619        return this;
620    }
621
622    /**
623     * Returns a shallow copy of the container's constant state to be used as
624     * the base state for {@link #mutate()}.
625     *
626     * @return a shallow copy of the constant state
627     */
628    DrawableContainerState cloneConstantState() {
629        return mDrawableContainerState;
630    }
631
632    /**
633     * @hide
634     */
635    public void clearMutated() {
636        super.clearMutated();
637        mDrawableContainerState.clearMutated();
638        mMutated = false;
639    }
640
641    /**
642     * A ConstantState that can contain several {@link Drawable}s.
643     *
644     * This class was made public to enable testing, and its visibility may change in a future
645     * release.
646     */
647    public abstract static class DrawableContainerState extends ConstantState {
648        final DrawableContainer mOwner;
649
650        Resources mSourceRes;
651        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
652        int mChangingConfigurations;
653        int mChildrenChangingConfigurations;
654
655        SparseArray<ConstantState> mDrawableFutures;
656        Drawable[] mDrawables;
657        int mNumChildren;
658
659        boolean mVariablePadding = false;
660        boolean mCheckedPadding;
661        Rect mConstantPadding;
662
663        boolean mConstantSize = false;
664        boolean mCheckedConstantSize;
665        int mConstantWidth;
666        int mConstantHeight;
667        int mConstantMinimumWidth;
668        int mConstantMinimumHeight;
669
670        boolean mCheckedOpacity;
671        int mOpacity;
672
673        boolean mCheckedStateful;
674        boolean mStateful;
675
676        boolean mCheckedConstantState;
677        boolean mCanConstantState;
678
679        boolean mDither = DEFAULT_DITHER;
680
681        boolean mMutated;
682        int mLayoutDirection;
683
684        int mEnterFadeDuration = 0;
685        int mExitFadeDuration = 0;
686
687        boolean mAutoMirrored;
688
689        ColorFilter mColorFilter;
690        boolean mHasColorFilter;
691
692        ColorStateList mTintList;
693        Mode mTintMode;
694        boolean mHasTintList;
695        boolean mHasTintMode;
696
697        DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
698                Resources res) {
699            mOwner = owner;
700            mSourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null);
701            mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
702
703            if (orig != null) {
704                mChangingConfigurations = orig.mChangingConfigurations;
705                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
706
707                mCheckedConstantState = true;
708                mCanConstantState = true;
709
710                mVariablePadding = orig.mVariablePadding;
711                mConstantSize = orig.mConstantSize;
712                mDither = orig.mDither;
713                mMutated = orig.mMutated;
714                mLayoutDirection = orig.mLayoutDirection;
715                mEnterFadeDuration = orig.mEnterFadeDuration;
716                mExitFadeDuration = orig.mExitFadeDuration;
717                mAutoMirrored = orig.mAutoMirrored;
718                mColorFilter = orig.mColorFilter;
719                mHasColorFilter = orig.mHasColorFilter;
720                mTintList = orig.mTintList;
721                mTintMode = orig.mTintMode;
722                mHasTintList = orig.mHasTintList;
723                mHasTintMode = orig.mHasTintMode;
724
725                if (orig.mDensity == mDensity) {
726                    if (orig.mCheckedPadding) {
727                        mConstantPadding = new Rect(orig.mConstantPadding);
728                        mCheckedPadding = true;
729                    }
730
731                    if (orig.mCheckedConstantSize) {
732                        mConstantWidth = orig.mConstantWidth;
733                        mConstantHeight = orig.mConstantHeight;
734                        mConstantMinimumWidth = orig.mConstantMinimumWidth;
735                        mConstantMinimumHeight = orig.mConstantMinimumHeight;
736                        mCheckedConstantSize = true;
737                    }
738                }
739
740                if (orig.mCheckedOpacity) {
741                    mOpacity = orig.mOpacity;
742                    mCheckedOpacity = true;
743                }
744
745                if (orig.mCheckedStateful) {
746                    mStateful = orig.mStateful;
747                    mCheckedStateful = true;
748                }
749
750                // Postpone cloning children and futures until we're absolutely
751                // sure that we're done computing values for the original state.
752                final Drawable[] origDr = orig.mDrawables;
753                mDrawables = new Drawable[origDr.length];
754                mNumChildren = orig.mNumChildren;
755
756                final SparseArray<ConstantState> origDf = orig.mDrawableFutures;
757                if (origDf != null) {
758                    mDrawableFutures = origDf.clone();
759                } else {
760                    mDrawableFutures = new SparseArray<>(mNumChildren);
761                }
762
763                // Create futures for drawables with constant states. If a
764                // drawable doesn't have a constant state, then we can't clone
765                // it and we'll have to reference the original.
766                final int N = mNumChildren;
767                for (int i = 0; i < N; i++) {
768                    if (origDr[i] != null) {
769                        final ConstantState cs = origDr[i].getConstantState();
770                        if (cs != null) {
771                            mDrawableFutures.put(i, cs);
772                        } else {
773                            mDrawables[i] = origDr[i];
774                        }
775                    }
776                }
777            } else {
778                mDrawables = new Drawable[10];
779                mNumChildren = 0;
780            }
781        }
782
783        @Override
784        public int getChangingConfigurations() {
785            return mChangingConfigurations | mChildrenChangingConfigurations;
786        }
787
788        public final int addChild(Drawable dr) {
789            final int pos = mNumChildren;
790
791            if (pos >= mDrawables.length) {
792                growArray(pos, pos+10);
793            }
794
795            dr.setVisible(false, true);
796            dr.setCallback(mOwner);
797
798            mDrawables[pos] = dr;
799            mNumChildren++;
800            mChildrenChangingConfigurations |= dr.getChangingConfigurations();
801            mCheckedStateful = false;
802            mCheckedOpacity = false;
803
804            mConstantPadding = null;
805            mCheckedPadding = false;
806            mCheckedConstantSize = false;
807
808            return pos;
809        }
810
811        final int getCapacity() {
812            return mDrawables.length;
813        }
814
815        private void createAllFutures() {
816            if (mDrawableFutures != null) {
817                final int futureCount = mDrawableFutures.size();
818                for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) {
819                    final int index = mDrawableFutures.keyAt(keyIndex);
820                    final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
821                    mDrawables[index] = prepareDrawable(cs.newDrawable(mSourceRes));
822                }
823
824                mDrawableFutures = null;
825            }
826        }
827
828        private Drawable prepareDrawable(Drawable child) {
829            child.setLayoutDirection(mLayoutDirection);
830            child.setCallback(mOwner);
831            child = child.mutate();
832            return child;
833        }
834
835        public final int getChildCount() {
836            return mNumChildren;
837        }
838
839        /*
840         * @deprecated Use {@link #getChild} instead.
841         */
842        public final Drawable[] getChildren() {
843            // Create all futures for backwards compatibility.
844            createAllFutures();
845
846            return mDrawables;
847        }
848
849        public final Drawable getChild(int index) {
850            final Drawable result = mDrawables[index];
851            if (result != null) {
852                return result;
853            }
854
855            // Prepare future drawable if necessary.
856            if (mDrawableFutures != null) {
857                final int keyIndex = mDrawableFutures.indexOfKey(index);
858                if (keyIndex >= 0) {
859                    final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
860                    final Drawable prepared = prepareDrawable(cs.newDrawable(mSourceRes));
861                    mDrawables[index] = prepared;
862                    mDrawableFutures.removeAt(keyIndex);
863                    if (mDrawableFutures.size() == 0) {
864                        mDrawableFutures = null;
865                    }
866                    return prepared;
867                }
868            }
869
870            return null;
871        }
872
873        final boolean setLayoutDirection(int layoutDirection, int currentIndex) {
874            boolean changed = false;
875
876            // No need to call createAllFutures, since future drawables will
877            // change layout direction when they are prepared.
878            final int N = mNumChildren;
879            final Drawable[] drawables = mDrawables;
880            for (int i = 0; i < N; i++) {
881                if (drawables[i] != null) {
882                    final boolean childChanged = drawables[i].setLayoutDirection(layoutDirection);
883                    if (i == currentIndex) {
884                        changed = childChanged;
885                    }
886                }
887            }
888
889            mLayoutDirection = layoutDirection;
890
891            return changed;
892        }
893
894        /**
895         * Updates the source density based on the resources used to inflate
896         * density-dependent values.
897         *
898         * @param res the resources used to inflate density-dependent values
899         */
900        final void updateDensity(Resources res) {
901            if (mSourceRes != null) {
902                mSourceRes = res;
903            }
904
905            // The density may have changed since the last update (if any). Any
906            // dimension-type attributes will need their default values scaled.
907            final int targetDensity = Drawable.resolveDensity(res, mDensity);
908            final int sourceDensity = mDensity;
909            mDensity = targetDensity;
910
911            if (sourceDensity != targetDensity) {
912                mCheckedConstantSize = false;
913                mCheckedPadding = false;
914            }
915        }
916
917        final void applyTheme(Theme theme) {
918            if (theme != null) {
919                createAllFutures();
920
921                final int N = mNumChildren;
922                final Drawable[] drawables = mDrawables;
923                for (int i = 0; i < N; i++) {
924                    if (drawables[i] != null && drawables[i].canApplyTheme()) {
925                        drawables[i].applyTheme(theme);
926
927                        // Update cached mask of child changing configurations.
928                        mChildrenChangingConfigurations |= drawables[i].getChangingConfigurations();
929                    }
930                }
931
932                updateDensity(theme.getResources());
933            }
934        }
935
936        @Override
937        public boolean canApplyTheme() {
938            final int N = mNumChildren;
939            final Drawable[] drawables = mDrawables;
940            for (int i = 0; i < N; i++) {
941                final Drawable d = drawables[i];
942                if (d != null) {
943                    if (d.canApplyTheme()) {
944                        return true;
945                    }
946                } else {
947                    final ConstantState future = mDrawableFutures.get(i);
948                    if (future != null && future.canApplyTheme()) {
949                        return true;
950                    }
951                }
952            }
953
954            return false;
955        }
956
957        private void mutate() {
958            // No need to call createAllFutures, since future drawables will
959            // mutate when they are prepared.
960            final int N = mNumChildren;
961            final Drawable[] drawables = mDrawables;
962            for (int i = 0; i < N; i++) {
963                if (drawables[i] != null) {
964                    drawables[i].mutate();
965                }
966            }
967
968            mMutated = true;
969        }
970
971        final void clearMutated() {
972            final int N = mNumChildren;
973            final Drawable[] drawables = mDrawables;
974            for (int i = 0; i < N; i++) {
975                if (drawables[i] != null) {
976                    drawables[i].clearMutated();
977                }
978            }
979
980            mMutated = false;
981        }
982
983        /**
984         * A boolean value indicating whether to use the maximum padding value
985         * of all frames in the set (false), or to use the padding value of the
986         * frame being shown (true). Default value is false.
987         */
988        public final void setVariablePadding(boolean variable) {
989            mVariablePadding = variable;
990        }
991
992        public final Rect getConstantPadding() {
993            if (mVariablePadding) {
994                return null;
995            }
996
997            if ((mConstantPadding != null) || mCheckedPadding) {
998                return mConstantPadding;
999            }
1000
1001            createAllFutures();
1002
1003            Rect r = null;
1004            final Rect t = new Rect();
1005            final int N = mNumChildren;
1006            final Drawable[] drawables = mDrawables;
1007            for (int i = 0; i < N; i++) {
1008                if (drawables[i].getPadding(t)) {
1009                    if (r == null) r = new Rect(0, 0, 0, 0);
1010                    if (t.left > r.left) r.left = t.left;
1011                    if (t.top > r.top) r.top = t.top;
1012                    if (t.right > r.right) r.right = t.right;
1013                    if (t.bottom > r.bottom) r.bottom = t.bottom;
1014                }
1015            }
1016
1017            mCheckedPadding = true;
1018            return (mConstantPadding = r);
1019        }
1020
1021        public final void setConstantSize(boolean constant) {
1022            mConstantSize = constant;
1023        }
1024
1025        public final boolean isConstantSize() {
1026            return mConstantSize;
1027        }
1028
1029        public final int getConstantWidth() {
1030            if (!mCheckedConstantSize) {
1031                computeConstantSize();
1032            }
1033
1034            return mConstantWidth;
1035        }
1036
1037        public final int getConstantHeight() {
1038            if (!mCheckedConstantSize) {
1039                computeConstantSize();
1040            }
1041
1042            return mConstantHeight;
1043        }
1044
1045        public final int getConstantMinimumWidth() {
1046            if (!mCheckedConstantSize) {
1047                computeConstantSize();
1048            }
1049
1050            return mConstantMinimumWidth;
1051        }
1052
1053        public final int getConstantMinimumHeight() {
1054            if (!mCheckedConstantSize) {
1055                computeConstantSize();
1056            }
1057
1058            return mConstantMinimumHeight;
1059        }
1060
1061        protected void computeConstantSize() {
1062            mCheckedConstantSize = true;
1063
1064            createAllFutures();
1065
1066            final int N = mNumChildren;
1067            final Drawable[] drawables = mDrawables;
1068            mConstantWidth = mConstantHeight = -1;
1069            mConstantMinimumWidth = mConstantMinimumHeight = 0;
1070            for (int i = 0; i < N; i++) {
1071                final Drawable dr = drawables[i];
1072                int s = dr.getIntrinsicWidth();
1073                if (s > mConstantWidth) mConstantWidth = s;
1074                s = dr.getIntrinsicHeight();
1075                if (s > mConstantHeight) mConstantHeight = s;
1076                s = dr.getMinimumWidth();
1077                if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
1078                s = dr.getMinimumHeight();
1079                if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
1080            }
1081        }
1082
1083        public final void setEnterFadeDuration(int duration) {
1084            mEnterFadeDuration = duration;
1085        }
1086
1087        public final int getEnterFadeDuration() {
1088            return mEnterFadeDuration;
1089        }
1090
1091        public final void setExitFadeDuration(int duration) {
1092            mExitFadeDuration = duration;
1093        }
1094
1095        public final int getExitFadeDuration() {
1096            return mExitFadeDuration;
1097        }
1098
1099        public final int getOpacity() {
1100            if (mCheckedOpacity) {
1101                return mOpacity;
1102            }
1103
1104            createAllFutures();
1105
1106            mCheckedOpacity = true;
1107
1108            final int N = mNumChildren;
1109            final Drawable[] drawables = mDrawables;
1110            int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
1111            for (int i = 1; i < N; i++) {
1112                op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
1113            }
1114
1115            mOpacity = op;
1116            return op;
1117        }
1118
1119        public final boolean isStateful() {
1120            if (mCheckedStateful) {
1121                return mStateful;
1122            }
1123
1124            createAllFutures();
1125
1126            mCheckedStateful = true;
1127
1128            final int N = mNumChildren;
1129            final Drawable[] drawables = mDrawables;
1130            for (int i = 0; i < N; i++) {
1131                if (drawables[i].isStateful()) {
1132                    mStateful = true;
1133                    return true;
1134                }
1135            }
1136
1137            mStateful = false;
1138            return false;
1139        }
1140
1141        public void growArray(int oldSize, int newSize) {
1142            Drawable[] newDrawables = new Drawable[newSize];
1143            System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
1144            mDrawables = newDrawables;
1145        }
1146
1147        public synchronized boolean canConstantState() {
1148            if (mCheckedConstantState) {
1149                return mCanConstantState;
1150            }
1151
1152            createAllFutures();
1153
1154            mCheckedConstantState = true;
1155
1156            final int N = mNumChildren;
1157            final Drawable[] drawables = mDrawables;
1158            for (int i = 0; i < N; i++) {
1159                if (drawables[i].getConstantState() == null) {
1160                    mCanConstantState = false;
1161                    return false;
1162                }
1163            }
1164
1165            mCanConstantState = true;
1166            return true;
1167        }
1168
1169        /** @hide */
1170        @Override
1171        public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
1172            final int N = mNumChildren;
1173            int pixelCount = 0;
1174            for (int i = 0; i < N; i++) {
1175                final ConstantState state = getChild(i).getConstantState();
1176                if (state != null) {
1177                    pixelCount += state.addAtlasableBitmaps(atlasList);
1178                }
1179            }
1180            return pixelCount;
1181        }
1182    }
1183
1184    protected void setConstantState(DrawableContainerState state) {
1185        mDrawableContainerState = state;
1186
1187        // The locally cached drawables may have changed.
1188        if (mCurIndex >= 0) {
1189            mCurrDrawable = state.getChild(mCurIndex);
1190            if (mCurrDrawable != null) {
1191                initializeDrawableForDisplay(mCurrDrawable);
1192            }
1193        }
1194
1195        // Clear out the last drawable. We don't have enough information to
1196        // propagate local state from the past.
1197        mLastIndex = -1;
1198        mLastDrawable = null;
1199    }
1200}
1201