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