DrawableContainer.java revision bddb843e793a279db99888dd78b4f74a362cbb8b
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     * A ConstantState that can contain several {@link Drawable}s.
578     *
579     * This class was made public to enable testing, and its visibility may change in a future
580     * release.
581     */
582    public abstract static class DrawableContainerState extends ConstantState {
583        final DrawableContainer mOwner;
584        final Resources mRes;
585
586        Theme mTheme;
587
588        SparseArray<ConstantStateFuture> mDrawableFutures;
589
590        int mChangingConfigurations;
591        int mChildrenChangingConfigurations;
592
593        Drawable[] mDrawables;
594        int mNumChildren;
595
596        boolean mVariablePadding;
597        boolean mPaddingChecked;
598        Rect mConstantPadding;
599
600        boolean mConstantSize;
601        boolean mComputedConstantSize;
602        int mConstantWidth;
603        int mConstantHeight;
604        int mConstantMinimumWidth;
605        int mConstantMinimumHeight;
606
607        boolean mCheckedOpacity;
608        int mOpacity;
609
610        boolean mCheckedStateful;
611        boolean mStateful;
612
613        boolean mCheckedConstantState;
614        boolean mCanConstantState;
615
616        boolean mDither = DEFAULT_DITHER;
617
618        boolean mMutated;
619        int mLayoutDirection;
620
621        int mEnterFadeDuration;
622        int mExitFadeDuration;
623
624        boolean mAutoMirrored;
625
626        ColorFilter mColorFilter;
627        boolean mHasColorFilter;
628
629        ColorStateList mTintList;
630        Mode mTintMode;
631        boolean mHasTintList;
632        boolean mHasTintMode;
633
634        DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
635                Resources res) {
636            mOwner = owner;
637            mRes = res;
638
639            if (orig != null) {
640                mChangingConfigurations = orig.mChangingConfigurations;
641                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
642
643                mCheckedConstantState = true;
644                mCanConstantState = true;
645
646                mVariablePadding = orig.mVariablePadding;
647                mConstantSize = orig.mConstantSize;
648                mDither = orig.mDither;
649                mMutated = orig.mMutated;
650                mLayoutDirection = orig.mLayoutDirection;
651                mEnterFadeDuration = orig.mEnterFadeDuration;
652                mExitFadeDuration = orig.mExitFadeDuration;
653                mAutoMirrored = orig.mAutoMirrored;
654                mColorFilter = orig.mColorFilter;
655                mHasColorFilter = orig.mHasColorFilter;
656                mTintList = orig.mTintList;
657                mTintMode = orig.mTintMode;
658                mHasTintList = orig.mHasTintList;
659                mHasTintMode = orig.mHasTintMode;
660
661                // Cloning the following values may require creating futures.
662                mConstantPadding = orig.getConstantPadding();
663                mPaddingChecked = true;
664
665                mConstantWidth = orig.getConstantWidth();
666                mConstantHeight = orig.getConstantHeight();
667                mConstantMinimumWidth = orig.getConstantMinimumWidth();
668                mConstantMinimumHeight = orig.getConstantMinimumHeight();
669                mComputedConstantSize = true;
670
671                mOpacity = orig.getOpacity();
672                mCheckedOpacity = true;
673
674                mStateful = orig.isStateful();
675                mCheckedStateful = true;
676
677                // Postpone cloning children and futures until we're absolutely
678                // sure that we're done computing values for the original state.
679                final Drawable[] origDr = orig.mDrawables;
680                mDrawables = new Drawable[origDr.length];
681                mNumChildren = orig.mNumChildren;
682
683                final SparseArray<ConstantStateFuture> origDf = orig.mDrawableFutures;
684                if (origDf != null) {
685                    mDrawableFutures = origDf.clone();
686                } else {
687                    mDrawableFutures = new SparseArray<ConstantStateFuture>(mNumChildren);
688                }
689
690                final int N = mNumChildren;
691                for (int i = 0; i < N; i++) {
692                    if (origDr[i] != null) {
693                        mDrawableFutures.put(i, new ConstantStateFuture(origDr[i]));
694                    }
695                }
696            } else {
697                mDrawables = new Drawable[10];
698                mNumChildren = 0;
699            }
700        }
701
702        @Override
703        public int getChangingConfigurations() {
704            return mChangingConfigurations | mChildrenChangingConfigurations;
705        }
706
707        public final int addChild(Drawable dr) {
708            final int pos = mNumChildren;
709
710            if (pos >= mDrawables.length) {
711                growArray(pos, pos+10);
712            }
713
714            dr.setVisible(false, true);
715            dr.setCallback(mOwner);
716
717            mDrawables[pos] = dr;
718            mNumChildren++;
719            mChildrenChangingConfigurations |= dr.getChangingConfigurations();
720            mCheckedStateful = false;
721            mCheckedOpacity = false;
722
723            mConstantPadding = null;
724            mPaddingChecked = false;
725            mComputedConstantSize = false;
726
727            return pos;
728        }
729
730        final int getCapacity() {
731            return mDrawables.length;
732        }
733
734        private final void createAllFutures() {
735            if (mDrawableFutures != null) {
736                final int futureCount = mDrawableFutures.size();
737                for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) {
738                    final int index = mDrawableFutures.keyAt(keyIndex);
739                    mDrawables[index] = mDrawableFutures.valueAt(keyIndex).get(this);
740                }
741
742                mDrawableFutures = null;
743            }
744        }
745
746        public final int getChildCount() {
747            return mNumChildren;
748        }
749
750        /*
751         * @deprecated Use {@link #getChild} instead.
752         */
753        public final Drawable[] getChildren() {
754            // Create all futures for backwards compatibility.
755            createAllFutures();
756
757            return mDrawables;
758        }
759
760        public final Drawable getChild(int index) {
761            final Drawable result = mDrawables[index];
762            if (result != null) {
763                return result;
764            }
765
766            // Prepare future drawable if necessary.
767            if (mDrawableFutures != null) {
768                final int keyIndex = mDrawableFutures.indexOfKey(index);
769                if (keyIndex >= 0) {
770                    final Drawable prepared = mDrawableFutures.valueAt(keyIndex).get(this);
771                    mDrawables[index] = prepared;
772                    mDrawableFutures.removeAt(keyIndex);
773                    return prepared;
774                }
775            }
776
777            return null;
778        }
779
780        final void setLayoutDirection(int layoutDirection) {
781            // No need to call createAllFutures, since future drawables will
782            // change layout direction when they are prepared.
783            final int N = mNumChildren;
784            final Drawable[] drawables = mDrawables;
785            for (int i = 0; i < N; i++) {
786                if (drawables[i] != null) {
787                    drawables[i].setLayoutDirection(layoutDirection);
788                }
789            }
790
791            mLayoutDirection = layoutDirection;
792        }
793
794        final void applyTheme(Theme theme) {
795            // No need to call createAllFutures, since future drawables will
796            // apply the theme when they are prepared.
797            final int N = mNumChildren;
798            final Drawable[] drawables = mDrawables;
799            for (int i = 0; i < N; i++) {
800                if (drawables[i] != null) {
801                    drawables[i].applyTheme(theme);
802                }
803            }
804
805            mTheme = theme;
806        }
807
808        @Override
809        public boolean canApplyTheme() {
810            final int N = mNumChildren;
811            final Drawable[] drawables = mDrawables;
812            for (int i = 0; i < N; i++) {
813                final Drawable d = drawables[i];
814                if (d != null) {
815                    if (d.canApplyTheme()) {
816                        return true;
817                    }
818                } else {
819                    final ConstantStateFuture future = mDrawableFutures.get(i);
820                    if (future != null && future.canApplyTheme()) {
821                        return true;
822                    }
823                }
824            }
825
826            return false;
827        }
828
829        final void mutate() {
830            // No need to call createAllFutures, since future drawables will
831            // mutate when they are prepared.
832            final int N = mNumChildren;
833            final Drawable[] drawables = mDrawables;
834            for (int i = 0; i < N; i++) {
835                if (drawables[i] != null) {
836                    drawables[i].mutate();
837                }
838            }
839
840            mMutated = true;
841        }
842
843        /**
844         * A boolean value indicating whether to use the maximum padding value
845         * of all frames in the set (false), or to use the padding value of the
846         * frame being shown (true). Default value is false.
847         */
848        public final void setVariablePadding(boolean variable) {
849            mVariablePadding = variable;
850        }
851
852        public final Rect getConstantPadding() {
853            if (mVariablePadding) {
854                return null;
855            }
856
857            if ((mConstantPadding != null) || mPaddingChecked) {
858                return mConstantPadding;
859            }
860
861            createAllFutures();
862
863            Rect r = null;
864            final Rect t = new Rect();
865            final int N = mNumChildren;
866            final Drawable[] drawables = mDrawables;
867            for (int i = 0; i < N; i++) {
868                if (drawables[i].getPadding(t)) {
869                    if (r == null) r = new Rect(0, 0, 0, 0);
870                    if (t.left > r.left) r.left = t.left;
871                    if (t.top > r.top) r.top = t.top;
872                    if (t.right > r.right) r.right = t.right;
873                    if (t.bottom > r.bottom) r.bottom = t.bottom;
874                }
875            }
876
877            mPaddingChecked = true;
878            return (mConstantPadding = r);
879        }
880
881        public final void setConstantSize(boolean constant) {
882            mConstantSize = constant;
883        }
884
885        public final boolean isConstantSize() {
886            return mConstantSize;
887        }
888
889        public final int getConstantWidth() {
890            if (!mComputedConstantSize) {
891                computeConstantSize();
892            }
893
894            return mConstantWidth;
895        }
896
897        public final int getConstantHeight() {
898            if (!mComputedConstantSize) {
899                computeConstantSize();
900            }
901
902            return mConstantHeight;
903        }
904
905        public final int getConstantMinimumWidth() {
906            if (!mComputedConstantSize) {
907                computeConstantSize();
908            }
909
910            return mConstantMinimumWidth;
911        }
912
913        public final int getConstantMinimumHeight() {
914            if (!mComputedConstantSize) {
915                computeConstantSize();
916            }
917
918            return mConstantMinimumHeight;
919        }
920
921        protected void computeConstantSize() {
922            mComputedConstantSize = true;
923
924            createAllFutures();
925
926            final int N = mNumChildren;
927            final Drawable[] drawables = mDrawables;
928            mConstantWidth = mConstantHeight = -1;
929            mConstantMinimumWidth = mConstantMinimumHeight = 0;
930            for (int i = 0; i < N; i++) {
931                final Drawable dr = drawables[i];
932                int s = dr.getIntrinsicWidth();
933                if (s > mConstantWidth) mConstantWidth = s;
934                s = dr.getIntrinsicHeight();
935                if (s > mConstantHeight) mConstantHeight = s;
936                s = dr.getMinimumWidth();
937                if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
938                s = dr.getMinimumHeight();
939                if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
940            }
941        }
942
943        public final void setEnterFadeDuration(int duration) {
944            mEnterFadeDuration = duration;
945        }
946
947        public final int getEnterFadeDuration() {
948            return mEnterFadeDuration;
949        }
950
951        public final void setExitFadeDuration(int duration) {
952            mExitFadeDuration = duration;
953        }
954
955        public final int getExitFadeDuration() {
956            return mExitFadeDuration;
957        }
958
959        public final int getOpacity() {
960            if (mCheckedOpacity) {
961                return mOpacity;
962            }
963
964            createAllFutures();
965
966            mCheckedOpacity = true;
967
968            final int N = mNumChildren;
969            final Drawable[] drawables = mDrawables;
970            int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
971            for (int i = 1; i < N; i++) {
972                op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
973            }
974
975            mOpacity = op;
976            return op;
977        }
978
979        public final boolean isStateful() {
980            if (mCheckedStateful) {
981                return mStateful;
982            }
983
984            createAllFutures();
985
986            mCheckedStateful = true;
987
988            final int N = mNumChildren;
989            final Drawable[] drawables = mDrawables;
990            for (int i = 0; i < N; i++) {
991                if (drawables[i].isStateful()) {
992                    mStateful = true;
993                    return true;
994                }
995            }
996
997            mStateful = false;
998            return false;
999        }
1000
1001        public void growArray(int oldSize, int newSize) {
1002            Drawable[] newDrawables = new Drawable[newSize];
1003            System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
1004            mDrawables = newDrawables;
1005        }
1006
1007        public synchronized boolean canConstantState() {
1008            if (mCheckedConstantState) {
1009                return mCanConstantState;
1010            }
1011
1012            createAllFutures();
1013
1014            mCheckedConstantState = true;
1015
1016            final int N = mNumChildren;
1017            final Drawable[] drawables = mDrawables;
1018            for (int i = 0; i < N; i++) {
1019                if (drawables[i].getConstantState() == null) {
1020                    mCanConstantState = false;
1021                    return false;
1022                }
1023            }
1024
1025            mCanConstantState = true;
1026            return true;
1027        }
1028
1029        /**
1030         * Class capable of cloning a Drawable from another Drawable's
1031         * ConstantState.
1032         */
1033        private static class ConstantStateFuture {
1034            private final ConstantState mConstantState;
1035
1036            private ConstantStateFuture(Drawable source) {
1037                mConstantState = source.getConstantState();
1038            }
1039
1040            /**
1041             * Obtains and prepares the Drawable represented by this future.
1042             *
1043             * @param state the container into which this future will be placed
1044             * @return a prepared Drawable
1045             */
1046            public Drawable get(DrawableContainerState state) {
1047                final Drawable result;
1048                if (state.mRes == null) {
1049                    result = mConstantState.newDrawable();
1050                } else if (state.mTheme == null) {
1051                    result = mConstantState.newDrawable(state.mRes);
1052                } else {
1053                    result = mConstantState.newDrawable(state.mRes, state.mTheme);
1054                }
1055                result.setLayoutDirection(state.mLayoutDirection);
1056                result.setCallback(state.mOwner);
1057
1058                if (state.mMutated) {
1059                    result.mutate();
1060                }
1061
1062                return result;
1063            }
1064
1065            /**
1066             * Whether the constant state wrapped by this future can apply a
1067             * theme.
1068             */
1069            public boolean canApplyTheme() {
1070                return mConstantState.canApplyTheme();
1071            }
1072        }
1073    }
1074
1075    protected void setConstantState(DrawableContainerState state) {
1076        mDrawableContainerState = state;
1077    }
1078}
1079