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