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.content.res.Resources;
     20 import android.graphics.Canvas;
     21 import android.graphics.ColorFilter;
     22 import android.graphics.Insets;
     23 import android.graphics.PixelFormat;
     24 import android.graphics.Rect;
     25 import android.os.SystemClock;
     26 
     27 /**
     28  * A helper class that contains several {@link Drawable}s and selects which one to use.
     29  *
     30  * You can subclass it to create your own DrawableContainers or directly use one its child classes.
     31  */
     32 public class DrawableContainer extends Drawable implements Drawable.Callback {
     33     private static final boolean DEBUG = false;
     34     private static final String TAG = "DrawableContainer";
     35 
     36     /**
     37      * To be proper, we should have a getter for dither (and alpha, etc.)
     38      * so that proxy classes like this can save/restore their delegates'
     39      * values, but we don't have getters. Since we do have setters
     40      * (e.g. setDither), which this proxy forwards on, we have to have some
     41      * default/initial setting.
     42      *
     43      * The initial setting for dither is now true, since it almost always seems
     44      * to improve the quality at negligible cost.
     45      */
     46     private static final boolean DEFAULT_DITHER = true;
     47     private DrawableContainerState mDrawableContainerState;
     48     private Drawable mCurrDrawable;
     49     private int mAlpha = 0xFF;
     50     private ColorFilter mColorFilter;
     51 
     52     private int mCurIndex = -1;
     53     private boolean mMutated;
     54 
     55     // Animations.
     56     private Runnable mAnimationRunnable;
     57     private long mEnterAnimationEnd;
     58     private long mExitAnimationEnd;
     59     private Drawable mLastDrawable;
     60 
     61     // overrides from Drawable
     62 
     63     @Override
     64     public void draw(Canvas canvas) {
     65         if (mCurrDrawable != null) {
     66             mCurrDrawable.draw(canvas);
     67         }
     68         if (mLastDrawable != null) {
     69             mLastDrawable.draw(canvas);
     70         }
     71     }
     72 
     73     @Override
     74     public int getChangingConfigurations() {
     75         return super.getChangingConfigurations()
     76                 | mDrawableContainerState.mChangingConfigurations
     77                 | mDrawableContainerState.mChildrenChangingConfigurations;
     78     }
     79 
     80     @Override
     81     public boolean getPadding(Rect padding) {
     82         final Rect r = mDrawableContainerState.getConstantPadding();
     83         if (r != null) {
     84             padding.set(r);
     85             return true;
     86         }
     87         if (mCurrDrawable != null) {
     88             return mCurrDrawable.getPadding(padding);
     89         } else {
     90             return super.getPadding(padding);
     91         }
     92     }
     93 
     94     /**
     95      * @hide
     96      */
     97     @Override
     98     public Insets getLayoutInsets() {
     99         return (mCurrDrawable == null) ? Insets.NONE : mCurrDrawable.getLayoutInsets();
    100     }
    101 
    102     @Override
    103     public void setAlpha(int alpha) {
    104         if (mAlpha != alpha) {
    105             mAlpha = alpha;
    106             if (mCurrDrawable != null) {
    107                 if (mEnterAnimationEnd == 0) {
    108                     mCurrDrawable.setAlpha(alpha);
    109                 } else {
    110                     animate(false);
    111                 }
    112             }
    113         }
    114     }
    115 
    116     @Override
    117     public void setDither(boolean dither) {
    118         if (mDrawableContainerState.mDither != dither) {
    119             mDrawableContainerState.mDither = dither;
    120             if (mCurrDrawable != null) {
    121                 mCurrDrawable.setDither(mDrawableContainerState.mDither);
    122             }
    123         }
    124     }
    125 
    126     @Override
    127     public void setColorFilter(ColorFilter cf) {
    128         if (mColorFilter != cf) {
    129             mColorFilter = cf;
    130             if (mCurrDrawable != null) {
    131                 mCurrDrawable.setColorFilter(cf);
    132             }
    133         }
    134     }
    135 
    136     /**
    137      * Change the global fade duration when a new drawable is entering
    138      * the scene.
    139      * @param ms The amount of time to fade in milliseconds.
    140      */
    141     public void setEnterFadeDuration(int ms) {
    142         mDrawableContainerState.mEnterFadeDuration = ms;
    143     }
    144 
    145     /**
    146      * Change the global fade duration when a new drawable is leaving
    147      * the scene.
    148      * @param ms The amount of time to fade in milliseconds.
    149      */
    150     public void setExitFadeDuration(int ms) {
    151         mDrawableContainerState.mExitFadeDuration = ms;
    152     }
    153 
    154     @Override
    155     protected void onBoundsChange(Rect bounds) {
    156         if (mLastDrawable != null) {
    157             mLastDrawable.setBounds(bounds);
    158         }
    159         if (mCurrDrawable != null) {
    160             mCurrDrawable.setBounds(bounds);
    161         }
    162     }
    163 
    164     @Override
    165     public boolean isStateful() {
    166         return mDrawableContainerState.isStateful();
    167     }
    168 
    169     @Override
    170     public void jumpToCurrentState() {
    171         boolean changed = false;
    172         if (mLastDrawable != null) {
    173             mLastDrawable.jumpToCurrentState();
    174             mLastDrawable = null;
    175             changed = true;
    176         }
    177         if (mCurrDrawable != null) {
    178             mCurrDrawable.jumpToCurrentState();
    179             mCurrDrawable.setAlpha(mAlpha);
    180         }
    181         if (mExitAnimationEnd != 0) {
    182             mExitAnimationEnd = 0;
    183             changed = true;
    184         }
    185         if (mEnterAnimationEnd != 0) {
    186             mEnterAnimationEnd = 0;
    187             changed = true;
    188         }
    189         if (changed) {
    190             invalidateSelf();
    191         }
    192     }
    193 
    194     @Override
    195     protected boolean onStateChange(int[] state) {
    196         if (mLastDrawable != null) {
    197             return mLastDrawable.setState(state);
    198         }
    199         if (mCurrDrawable != null) {
    200             return mCurrDrawable.setState(state);
    201         }
    202         return false;
    203     }
    204 
    205     @Override
    206     protected boolean onLevelChange(int level) {
    207         if (mLastDrawable != null) {
    208             return mLastDrawable.setLevel(level);
    209         }
    210         if (mCurrDrawable != null) {
    211             return mCurrDrawable.setLevel(level);
    212         }
    213         return false;
    214     }
    215 
    216     @Override
    217     public int getIntrinsicWidth() {
    218         if (mDrawableContainerState.isConstantSize()) {
    219             return mDrawableContainerState.getConstantWidth();
    220         }
    221         return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1;
    222     }
    223 
    224     @Override
    225     public int getIntrinsicHeight() {
    226         if (mDrawableContainerState.isConstantSize()) {
    227             return mDrawableContainerState.getConstantHeight();
    228         }
    229         return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1;
    230     }
    231 
    232     @Override
    233     public int getMinimumWidth() {
    234         if (mDrawableContainerState.isConstantSize()) {
    235             return mDrawableContainerState.getConstantMinimumWidth();
    236         }
    237         return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0;
    238     }
    239 
    240     @Override
    241     public int getMinimumHeight() {
    242         if (mDrawableContainerState.isConstantSize()) {
    243             return mDrawableContainerState.getConstantMinimumHeight();
    244         }
    245         return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0;
    246     }
    247 
    248     public void invalidateDrawable(Drawable who) {
    249         if (who == mCurrDrawable && getCallback() != null) {
    250             getCallback().invalidateDrawable(this);
    251         }
    252     }
    253 
    254     public void scheduleDrawable(Drawable who, Runnable what, long when) {
    255         if (who == mCurrDrawable && getCallback() != null) {
    256             getCallback().scheduleDrawable(this, what, when);
    257         }
    258     }
    259 
    260     public void unscheduleDrawable(Drawable who, Runnable what) {
    261         if (who == mCurrDrawable && getCallback() != null) {
    262             getCallback().unscheduleDrawable(this, what);
    263         }
    264     }
    265 
    266     @Override
    267     public boolean setVisible(boolean visible, boolean restart) {
    268         boolean changed = super.setVisible(visible, restart);
    269         if (mLastDrawable != null) {
    270             mLastDrawable.setVisible(visible, restart);
    271         }
    272         if (mCurrDrawable != null) {
    273             mCurrDrawable.setVisible(visible, restart);
    274         }
    275         return changed;
    276     }
    277 
    278     @Override
    279     public int getOpacity() {
    280         return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT :
    281                 mDrawableContainerState.getOpacity();
    282     }
    283 
    284     public boolean selectDrawable(int idx) {
    285         if (idx == mCurIndex) {
    286             return false;
    287         }
    288 
    289         final long now = SystemClock.uptimeMillis();
    290 
    291         if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + idx
    292                 + ": exit=" + mDrawableContainerState.mExitFadeDuration
    293                 + " enter=" + mDrawableContainerState.mEnterFadeDuration);
    294 
    295         if (mDrawableContainerState.mExitFadeDuration > 0) {
    296             if (mLastDrawable != null) {
    297                 mLastDrawable.setVisible(false, false);
    298             }
    299             if (mCurrDrawable != null) {
    300                 mLastDrawable = mCurrDrawable;
    301                 mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
    302             } else {
    303                 mLastDrawable = null;
    304                 mExitAnimationEnd = 0;
    305             }
    306         } else if (mCurrDrawable != null) {
    307             mCurrDrawable.setVisible(false, false);
    308         }
    309 
    310         if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
    311             Drawable d = mDrawableContainerState.mDrawables[idx];
    312             mCurrDrawable = d;
    313             mCurIndex = idx;
    314             if (d != null) {
    315                 if (mDrawableContainerState.mEnterFadeDuration > 0) {
    316                     mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
    317                 } else {
    318                     d.setAlpha(mAlpha);
    319                 }
    320                 d.setVisible(isVisible(), true);
    321                 d.setDither(mDrawableContainerState.mDither);
    322                 d.setColorFilter(mColorFilter);
    323                 d.setState(getState());
    324                 d.setLevel(getLevel());
    325                 d.setBounds(getBounds());
    326             }
    327         } else {
    328             mCurrDrawable = null;
    329             mCurIndex = -1;
    330         }
    331 
    332         if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
    333             if (mAnimationRunnable == null) {
    334                 mAnimationRunnable = new Runnable() {
    335                     @Override public void run() {
    336                         animate(true);
    337                         invalidateSelf();
    338                     }
    339                 };
    340             } else {
    341                 unscheduleSelf(mAnimationRunnable);
    342             }
    343             // Compute first frame and schedule next animation.
    344             animate(true);
    345         }
    346 
    347         invalidateSelf();
    348 
    349         return true;
    350     }
    351 
    352     void animate(boolean schedule) {
    353         final long now = SystemClock.uptimeMillis();
    354         boolean animating = false;
    355         if (mCurrDrawable != null) {
    356             if (mEnterAnimationEnd != 0) {
    357                 if (mEnterAnimationEnd <= now) {
    358                     mCurrDrawable.setAlpha(mAlpha);
    359                     mEnterAnimationEnd = 0;
    360                 } else {
    361                     int animAlpha = (int)((mEnterAnimationEnd-now)*255)
    362                             / mDrawableContainerState.mEnterFadeDuration;
    363                     if (DEBUG) android.util.Log.i(TAG, toString() + " cur alpha " + animAlpha);
    364                     mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255);
    365                     animating = true;
    366                 }
    367             }
    368         } else {
    369             mEnterAnimationEnd = 0;
    370         }
    371         if (mLastDrawable != null) {
    372             if (mExitAnimationEnd != 0) {
    373                 if (mExitAnimationEnd <= now) {
    374                     mLastDrawable.setVisible(false, false);
    375                     mLastDrawable = null;
    376                     mExitAnimationEnd = 0;
    377                 } else {
    378                     int animAlpha = (int)((mExitAnimationEnd-now)*255)
    379                             / mDrawableContainerState.mExitFadeDuration;
    380                     if (DEBUG) android.util.Log.i(TAG, toString() + " last alpha " + animAlpha);
    381                     mLastDrawable.setAlpha((animAlpha*mAlpha)/255);
    382                     animating = true;
    383                 }
    384             }
    385         } else {
    386             mExitAnimationEnd = 0;
    387         }
    388 
    389         if (schedule && animating) {
    390             scheduleSelf(mAnimationRunnable, now + 1000/60);
    391         }
    392     }
    393 
    394     @Override
    395     public Drawable getCurrent() {
    396         return mCurrDrawable;
    397     }
    398 
    399     @Override
    400     public ConstantState getConstantState() {
    401         if (mDrawableContainerState.canConstantState()) {
    402             mDrawableContainerState.mChangingConfigurations = getChangingConfigurations();
    403             return mDrawableContainerState;
    404         }
    405         return null;
    406     }
    407 
    408     @Override
    409     public Drawable mutate() {
    410         if (!mMutated && super.mutate() == this) {
    411             final int N = mDrawableContainerState.getChildCount();
    412             final Drawable[] drawables = mDrawableContainerState.getChildren();
    413             for (int i = 0; i < N; i++) {
    414                 if (drawables[i] != null) drawables[i].mutate();
    415             }
    416             mMutated = true;
    417         }
    418         return this;
    419     }
    420 
    421     /**
    422      * A ConstantState that can contain several {@link Drawable}s.
    423      *
    424      * This class was made public to enable testing, and its visibility may change in a future
    425      * release.
    426      */
    427     public abstract static class DrawableContainerState extends ConstantState {
    428         final DrawableContainer mOwner;
    429 
    430         int         mChangingConfigurations;
    431         int         mChildrenChangingConfigurations;
    432 
    433         Drawable[]  mDrawables;
    434         int         mNumChildren;
    435 
    436         boolean     mVariablePadding = false;
    437         Rect        mConstantPadding = null;
    438 
    439         boolean     mConstantSize = false;
    440         boolean     mComputedConstantSize = false;
    441         int         mConstantWidth;
    442         int         mConstantHeight;
    443         int         mConstantMinimumWidth;
    444         int         mConstantMinimumHeight;
    445 
    446         boolean     mHaveOpacity = false;
    447         int         mOpacity;
    448 
    449         boolean     mHaveStateful = false;
    450         boolean     mStateful;
    451 
    452         boolean     mCheckedConstantState;
    453         boolean     mCanConstantState;
    454 
    455         boolean     mPaddingChecked = false;
    456 
    457         boolean     mDither = DEFAULT_DITHER;
    458 
    459         int         mEnterFadeDuration;
    460         int         mExitFadeDuration;
    461 
    462         DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
    463                 Resources res) {
    464             mOwner = owner;
    465 
    466             if (orig != null) {
    467                 mChangingConfigurations = orig.mChangingConfigurations;
    468                 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
    469 
    470                 final Drawable[] origDr = orig.mDrawables;
    471 
    472                 mDrawables = new Drawable[origDr.length];
    473                 mNumChildren = orig.mNumChildren;
    474 
    475                 final int N = mNumChildren;
    476                 for (int i=0; i<N; i++) {
    477                     if (res != null) {
    478                         mDrawables[i] = origDr[i].getConstantState().newDrawable(res);
    479                     } else {
    480                         mDrawables[i] = origDr[i].getConstantState().newDrawable();
    481                     }
    482                     mDrawables[i].setCallback(owner);
    483                 }
    484 
    485                 mCheckedConstantState = mCanConstantState = true;
    486                 mVariablePadding = orig.mVariablePadding;
    487                 if (orig.mConstantPadding != null) {
    488                     mConstantPadding = new Rect(orig.mConstantPadding);
    489                 }
    490                 mConstantSize = orig.mConstantSize;
    491                 mComputedConstantSize = orig.mComputedConstantSize;
    492                 mConstantWidth = orig.mConstantWidth;
    493                 mConstantHeight = orig.mConstantHeight;
    494 
    495                 mHaveOpacity = orig.mHaveOpacity;
    496                 mOpacity = orig.mOpacity;
    497                 mHaveStateful = orig.mHaveStateful;
    498                 mStateful = orig.mStateful;
    499 
    500                 mDither = orig.mDither;
    501 
    502                 mEnterFadeDuration = orig.mEnterFadeDuration;
    503                 mExitFadeDuration = orig.mExitFadeDuration;
    504 
    505             } else {
    506                 mDrawables = new Drawable[10];
    507                 mNumChildren = 0;
    508                 mCheckedConstantState = mCanConstantState = false;
    509             }
    510         }
    511 
    512         @Override
    513         public int getChangingConfigurations() {
    514             return mChangingConfigurations;
    515         }
    516 
    517         public final int addChild(Drawable dr) {
    518             final int pos = mNumChildren;
    519 
    520             if (pos >= mDrawables.length) {
    521                 growArray(pos, pos+10);
    522             }
    523 
    524             dr.setVisible(false, true);
    525             dr.setCallback(mOwner);
    526 
    527             mDrawables[pos] = dr;
    528             mNumChildren++;
    529             mChildrenChangingConfigurations |= dr.getChangingConfigurations();
    530             mHaveOpacity = false;
    531             mHaveStateful = false;
    532 
    533             mConstantPadding = null;
    534             mPaddingChecked = false;
    535             mComputedConstantSize = false;
    536 
    537             return pos;
    538         }
    539 
    540         public final int getChildCount() {
    541             return mNumChildren;
    542         }
    543 
    544         public final Drawable[] getChildren() {
    545             return mDrawables;
    546         }
    547 
    548         /** A boolean value indicating whether to use the maximum padding value of
    549           * all frames in the set (false), or to use the padding value of the frame
    550           * being shown (true). Default value is false.
    551           */
    552         public final void setVariablePadding(boolean variable) {
    553             mVariablePadding = variable;
    554         }
    555 
    556         public final Rect getConstantPadding() {
    557             if (mVariablePadding) {
    558                 return null;
    559             }
    560             if (mConstantPadding != null || mPaddingChecked) {
    561                 return mConstantPadding;
    562             }
    563 
    564             Rect r = null;
    565             final Rect t = new Rect();
    566             final int N = getChildCount();
    567             final Drawable[] drawables = mDrawables;
    568             for (int i = 0; i < N; i++) {
    569                 if (drawables[i].getPadding(t)) {
    570                     if (r == null) r = new Rect(0, 0, 0, 0);
    571                     if (t.left > r.left) r.left = t.left;
    572                     if (t.top > r.top) r.top = t.top;
    573                     if (t.right > r.right) r.right = t.right;
    574                     if (t.bottom > r.bottom) r.bottom = t.bottom;
    575                 }
    576             }
    577             mPaddingChecked = true;
    578             return (mConstantPadding = r);
    579         }
    580 
    581         public final void setConstantSize(boolean constant) {
    582             mConstantSize = constant;
    583         }
    584 
    585         public final boolean isConstantSize() {
    586             return mConstantSize;
    587         }
    588 
    589         public final int getConstantWidth() {
    590             if (!mComputedConstantSize) {
    591                 computeConstantSize();
    592             }
    593 
    594             return mConstantWidth;
    595         }
    596 
    597         public final int getConstantHeight() {
    598             if (!mComputedConstantSize) {
    599                 computeConstantSize();
    600             }
    601 
    602             return mConstantHeight;
    603         }
    604 
    605         public final int getConstantMinimumWidth() {
    606             if (!mComputedConstantSize) {
    607                 computeConstantSize();
    608             }
    609 
    610             return mConstantMinimumWidth;
    611         }
    612 
    613         public final int getConstantMinimumHeight() {
    614             if (!mComputedConstantSize) {
    615                 computeConstantSize();
    616             }
    617 
    618             return mConstantMinimumHeight;
    619         }
    620 
    621         protected void computeConstantSize() {
    622             mComputedConstantSize = true;
    623 
    624             final int N = getChildCount();
    625             final Drawable[] drawables = mDrawables;
    626             mConstantWidth = mConstantHeight = -1;
    627             mConstantMinimumWidth = mConstantMinimumHeight = 0;
    628             for (int i = 0; i < N; i++) {
    629                 Drawable dr = drawables[i];
    630                 int s = dr.getIntrinsicWidth();
    631                 if (s > mConstantWidth) mConstantWidth = s;
    632                 s = dr.getIntrinsicHeight();
    633                 if (s > mConstantHeight) mConstantHeight = s;
    634                 s = dr.getMinimumWidth();
    635                 if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
    636                 s = dr.getMinimumHeight();
    637                 if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
    638             }
    639         }
    640 
    641         public final void setEnterFadeDuration(int duration) {
    642             mEnterFadeDuration = duration;
    643         }
    644 
    645         public final int getEnterFadeDuration() {
    646             return mEnterFadeDuration;
    647         }
    648 
    649         public final void setExitFadeDuration(int duration) {
    650             mExitFadeDuration = duration;
    651         }
    652 
    653         public final int getExitFadeDuration() {
    654             return mExitFadeDuration;
    655         }
    656 
    657         public final int getOpacity() {
    658             if (mHaveOpacity) {
    659                 return mOpacity;
    660             }
    661 
    662             final int N = getChildCount();
    663             final Drawable[] drawables = mDrawables;
    664             int op = N > 0 ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
    665             for (int i = 1; i < N; i++) {
    666                 op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
    667             }
    668             mOpacity = op;
    669             mHaveOpacity = true;
    670             return op;
    671         }
    672 
    673         public final boolean isStateful() {
    674             if (mHaveStateful) {
    675                 return mStateful;
    676             }
    677 
    678             boolean stateful = false;
    679             final int N = getChildCount();
    680             for (int i = 0; i < N; i++) {
    681                 if (mDrawables[i].isStateful()) {
    682                     stateful = true;
    683                     break;
    684                 }
    685             }
    686 
    687             mStateful = stateful;
    688             mHaveStateful = true;
    689             return stateful;
    690         }
    691 
    692         public void growArray(int oldSize, int newSize) {
    693             Drawable[] newDrawables = new Drawable[newSize];
    694             System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
    695             mDrawables = newDrawables;
    696         }
    697 
    698         public synchronized boolean canConstantState() {
    699             if (!mCheckedConstantState) {
    700                 mCanConstantState = true;
    701                 final int N = mNumChildren;
    702                 for (int i=0; i<N; i++) {
    703                     if (mDrawables[i].getConstantState() == null) {
    704                         mCanConstantState = false;
    705                         break;
    706                     }
    707                 }
    708                 mCheckedConstantState = true;
    709             }
    710 
    711             return mCanConstantState;
    712         }
    713     }
    714 
    715     protected void setConstantState(DrawableContainerState state)
    716     {
    717         mDrawableContainerState = state;
    718     }
    719 }
    720