DrawableContainer.java revision 5291a9571580d5147331adbdefb017fa395df505
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            mCheckedConstantState = false;
808
809            return pos;
810        }
811
812        final int getCapacity() {
813            return mDrawables.length;
814        }
815
816        private void createAllFutures() {
817            if (mDrawableFutures != null) {
818                final int futureCount = mDrawableFutures.size();
819                for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) {
820                    final int index = mDrawableFutures.keyAt(keyIndex);
821                    final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
822                    mDrawables[index] = prepareDrawable(cs.newDrawable(mSourceRes));
823                }
824
825                mDrawableFutures = null;
826            }
827        }
828
829        private Drawable prepareDrawable(Drawable child) {
830            child.setLayoutDirection(mLayoutDirection);
831            child.setCallback(mOwner);
832            child = child.mutate();
833            return child;
834        }
835
836        public final int getChildCount() {
837            return mNumChildren;
838        }
839
840        /*
841         * @deprecated Use {@link #getChild} instead.
842         */
843        public final Drawable[] getChildren() {
844            // Create all futures for backwards compatibility.
845            createAllFutures();
846
847            return mDrawables;
848        }
849
850        public final Drawable getChild(int index) {
851            final Drawable result = mDrawables[index];
852            if (result != null) {
853                return result;
854            }
855
856            // Prepare future drawable if necessary.
857            if (mDrawableFutures != null) {
858                final int keyIndex = mDrawableFutures.indexOfKey(index);
859                if (keyIndex >= 0) {
860                    final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
861                    final Drawable prepared = prepareDrawable(cs.newDrawable(mSourceRes));
862                    mDrawables[index] = prepared;
863                    mDrawableFutures.removeAt(keyIndex);
864                    if (mDrawableFutures.size() == 0) {
865                        mDrawableFutures = null;
866                    }
867                    return prepared;
868                }
869            }
870
871            return null;
872        }
873
874        final boolean setLayoutDirection(int layoutDirection, int currentIndex) {
875            boolean changed = false;
876
877            // No need to call createAllFutures, since future drawables will
878            // change layout direction 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                    final boolean childChanged = drawables[i].setLayoutDirection(layoutDirection);
884                    if (i == currentIndex) {
885                        changed = childChanged;
886                    }
887                }
888            }
889
890            mLayoutDirection = layoutDirection;
891
892            return changed;
893        }
894
895        /**
896         * Updates the source density based on the resources used to inflate
897         * density-dependent values.
898         *
899         * @param res the resources used to inflate density-dependent values
900         */
901        final void updateDensity(Resources res) {
902            if (res != null) {
903                mSourceRes = res;
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
918        final void applyTheme(Theme theme) {
919            if (theme != null) {
920                createAllFutures();
921
922                final int N = mNumChildren;
923                final Drawable[] drawables = mDrawables;
924                for (int i = 0; i < N; i++) {
925                    if (drawables[i] != null && drawables[i].canApplyTheme()) {
926                        drawables[i].applyTheme(theme);
927
928                        // Update cached mask of child changing configurations.
929                        mChildrenChangingConfigurations |= drawables[i].getChangingConfigurations();
930                    }
931                }
932
933                updateDensity(theme.getResources());
934            }
935        }
936
937        @Override
938        public boolean canApplyTheme() {
939            final int N = mNumChildren;
940            final Drawable[] drawables = mDrawables;
941            for (int i = 0; i < N; i++) {
942                final Drawable d = drawables[i];
943                if (d != null) {
944                    if (d.canApplyTheme()) {
945                        return true;
946                    }
947                } else {
948                    final ConstantState future = mDrawableFutures.get(i);
949                    if (future != null && future.canApplyTheme()) {
950                        return true;
951                    }
952                }
953            }
954
955            return false;
956        }
957
958        private void mutate() {
959            // No need to call createAllFutures, since future drawables will
960            // mutate when they are prepared.
961            final int N = mNumChildren;
962            final Drawable[] drawables = mDrawables;
963            for (int i = 0; i < N; i++) {
964                if (drawables[i] != null) {
965                    drawables[i].mutate();
966                }
967            }
968
969            mMutated = true;
970        }
971
972        final void clearMutated() {
973            final int N = mNumChildren;
974            final Drawable[] drawables = mDrawables;
975            for (int i = 0; i < N; i++) {
976                if (drawables[i] != null) {
977                    drawables[i].clearMutated();
978                }
979            }
980
981            mMutated = false;
982        }
983
984        /**
985         * A boolean value indicating whether to use the maximum padding value
986         * of all frames in the set (false), or to use the padding value of the
987         * frame being shown (true). Default value is false.
988         */
989        public final void setVariablePadding(boolean variable) {
990            mVariablePadding = variable;
991        }
992
993        public final Rect getConstantPadding() {
994            if (mVariablePadding) {
995                return null;
996            }
997
998            if ((mConstantPadding != null) || mCheckedPadding) {
999                return mConstantPadding;
1000            }
1001
1002            createAllFutures();
1003
1004            Rect r = null;
1005            final Rect t = new Rect();
1006            final int N = mNumChildren;
1007            final Drawable[] drawables = mDrawables;
1008            for (int i = 0; i < N; i++) {
1009                if (drawables[i].getPadding(t)) {
1010                    if (r == null) r = new Rect(0, 0, 0, 0);
1011                    if (t.left > r.left) r.left = t.left;
1012                    if (t.top > r.top) r.top = t.top;
1013                    if (t.right > r.right) r.right = t.right;
1014                    if (t.bottom > r.bottom) r.bottom = t.bottom;
1015                }
1016            }
1017
1018            mCheckedPadding = true;
1019            return (mConstantPadding = r);
1020        }
1021
1022        public final void setConstantSize(boolean constant) {
1023            mConstantSize = constant;
1024        }
1025
1026        public final boolean isConstantSize() {
1027            return mConstantSize;
1028        }
1029
1030        public final int getConstantWidth() {
1031            if (!mCheckedConstantSize) {
1032                computeConstantSize();
1033            }
1034
1035            return mConstantWidth;
1036        }
1037
1038        public final int getConstantHeight() {
1039            if (!mCheckedConstantSize) {
1040                computeConstantSize();
1041            }
1042
1043            return mConstantHeight;
1044        }
1045
1046        public final int getConstantMinimumWidth() {
1047            if (!mCheckedConstantSize) {
1048                computeConstantSize();
1049            }
1050
1051            return mConstantMinimumWidth;
1052        }
1053
1054        public final int getConstantMinimumHeight() {
1055            if (!mCheckedConstantSize) {
1056                computeConstantSize();
1057            }
1058
1059            return mConstantMinimumHeight;
1060        }
1061
1062        protected void computeConstantSize() {
1063            mCheckedConstantSize = true;
1064
1065            createAllFutures();
1066
1067            final int N = mNumChildren;
1068            final Drawable[] drawables = mDrawables;
1069            mConstantWidth = mConstantHeight = -1;
1070            mConstantMinimumWidth = mConstantMinimumHeight = 0;
1071            for (int i = 0; i < N; i++) {
1072                final Drawable dr = drawables[i];
1073                int s = dr.getIntrinsicWidth();
1074                if (s > mConstantWidth) mConstantWidth = s;
1075                s = dr.getIntrinsicHeight();
1076                if (s > mConstantHeight) mConstantHeight = s;
1077                s = dr.getMinimumWidth();
1078                if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
1079                s = dr.getMinimumHeight();
1080                if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
1081            }
1082        }
1083
1084        public final void setEnterFadeDuration(int duration) {
1085            mEnterFadeDuration = duration;
1086        }
1087
1088        public final int getEnterFadeDuration() {
1089            return mEnterFadeDuration;
1090        }
1091
1092        public final void setExitFadeDuration(int duration) {
1093            mExitFadeDuration = duration;
1094        }
1095
1096        public final int getExitFadeDuration() {
1097            return mExitFadeDuration;
1098        }
1099
1100        public final int getOpacity() {
1101            if (mCheckedOpacity) {
1102                return mOpacity;
1103            }
1104
1105            createAllFutures();
1106
1107            mCheckedOpacity = true;
1108
1109            final int N = mNumChildren;
1110            final Drawable[] drawables = mDrawables;
1111            int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
1112            for (int i = 1; i < N; i++) {
1113                op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
1114            }
1115
1116            mOpacity = op;
1117            return op;
1118        }
1119
1120        public final boolean isStateful() {
1121            if (mCheckedStateful) {
1122                return mStateful;
1123            }
1124
1125            createAllFutures();
1126
1127            mCheckedStateful = true;
1128
1129            final int N = mNumChildren;
1130            final Drawable[] drawables = mDrawables;
1131            for (int i = 0; i < N; i++) {
1132                if (drawables[i].isStateful()) {
1133                    mStateful = true;
1134                    return true;
1135                }
1136            }
1137
1138            mStateful = false;
1139            return false;
1140        }
1141
1142        public void growArray(int oldSize, int newSize) {
1143            Drawable[] newDrawables = new Drawable[newSize];
1144            System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
1145            mDrawables = newDrawables;
1146        }
1147
1148        public synchronized boolean canConstantState() {
1149            if (mCheckedConstantState) {
1150                return mCanConstantState;
1151            }
1152
1153            createAllFutures();
1154
1155            mCheckedConstantState = true;
1156
1157            final int N = mNumChildren;
1158            final Drawable[] drawables = mDrawables;
1159            for (int i = 0; i < N; i++) {
1160                if (drawables[i].getConstantState() == null) {
1161                    mCanConstantState = false;
1162                    return false;
1163                }
1164            }
1165
1166            mCanConstantState = true;
1167            return true;
1168        }
1169
1170        /** @hide */
1171        @Override
1172        public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
1173            final int N = mNumChildren;
1174            int pixelCount = 0;
1175            for (int i = 0; i < N; i++) {
1176                final ConstantState state = getChild(i).getConstantState();
1177                if (state != null) {
1178                    pixelCount += state.addAtlasableBitmaps(atlasList);
1179                }
1180            }
1181            return pixelCount;
1182        }
1183    }
1184
1185    protected void setConstantState(DrawableContainerState state) {
1186        mDrawableContainerState = state;
1187
1188        // The locally cached drawables may have changed.
1189        if (mCurIndex >= 0) {
1190            mCurrDrawable = state.getChild(mCurIndex);
1191            if (mCurrDrawable != null) {
1192                initializeDrawableForDisplay(mCurrDrawable);
1193            }
1194        }
1195
1196        // Clear out the last drawable. We don't have enough information to
1197        // propagate local state from the past.
1198        mLastIndex = -1;
1199        mLastDrawable = null;
1200    }
1201}
1202