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