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