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