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