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