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