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