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 org.xmlpull.v1.XmlPullParser;
     20 import org.xmlpull.v1.XmlPullParserException;
     21 
     22 import android.content.res.Resources;
     23 import android.content.res.TypedArray;
     24 import android.graphics.*;
     25 import android.util.AttributeSet;
     26 import android.view.View;
     27 
     28 import java.io.IOException;
     29 
     30 /**
     31  * A Drawable that manages an array of other Drawables. These are drawn in array
     32  * order, so the element with the largest index will be drawn on top.
     33  * <p>
     34  * It can be defined in an XML file with the <code>&lt;layer-list></code> element.
     35  * Each Drawable in the layer is defined in a nested <code>&lt;item></code>. For more
     36  * information, see the guide to <a
     37  * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
     38  *
     39  * @attr ref android.R.styleable#LayerDrawableItem_left
     40  * @attr ref android.R.styleable#LayerDrawableItem_top
     41  * @attr ref android.R.styleable#LayerDrawableItem_right
     42  * @attr ref android.R.styleable#LayerDrawableItem_bottom
     43  * @attr ref android.R.styleable#LayerDrawableItem_drawable
     44  * @attr ref android.R.styleable#LayerDrawableItem_id
     45 */
     46 public class LayerDrawable extends Drawable implements Drawable.Callback {
     47     LayerState mLayerState;
     48 
     49     private int[] mPaddingL;
     50     private int[] mPaddingT;
     51     private int[] mPaddingR;
     52     private int[] mPaddingB;
     53 
     54     private final Rect mTmpRect = new Rect();
     55     private boolean mMutated;
     56 
     57     /**
     58      * Create a new layer drawable with the list of specified layers.
     59      *
     60      * @param layers A list of drawables to use as layers in this new drawable.
     61      */
     62     public LayerDrawable(Drawable[] layers) {
     63         this(layers, null);
     64     }
     65 
     66     /**
     67      * Create a new layer drawable with the specified list of layers and the specified
     68      * constant state.
     69      *
     70      * @param layers The list of layers to add to this drawable.
     71      * @param state The constant drawable state.
     72      */
     73     LayerDrawable(Drawable[] layers, LayerState state) {
     74         this(state, null);
     75         int length = layers.length;
     76         ChildDrawable[] r = new ChildDrawable[length];
     77 
     78         for (int i = 0; i < length; i++) {
     79             r[i] = new ChildDrawable();
     80             r[i].mDrawable = layers[i];
     81             layers[i].setCallback(this);
     82             mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations();
     83         }
     84         mLayerState.mNum = length;
     85         mLayerState.mChildren = r;
     86 
     87         ensurePadding();
     88     }
     89 
     90     LayerDrawable() {
     91         this((LayerState) null, null);
     92     }
     93 
     94     LayerDrawable(LayerState state, Resources res) {
     95         LayerState as = createConstantState(state, res);
     96         mLayerState = as;
     97         if (as.mNum > 0) {
     98             ensurePadding();
     99         }
    100     }
    101 
    102     LayerState createConstantState(LayerState state, Resources res) {
    103         return new LayerState(state, this, res);
    104     }
    105 
    106     @Override
    107     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
    108             throws XmlPullParserException, IOException {
    109         super.inflate(r, parser, attrs);
    110 
    111         int type;
    112 
    113         final int innerDepth = parser.getDepth() + 1;
    114         int depth;
    115         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    116                 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
    117             if (type != XmlPullParser.START_TAG) {
    118                 continue;
    119             }
    120 
    121             if (depth > innerDepth || !parser.getName().equals("item")) {
    122                 continue;
    123             }
    124 
    125             TypedArray a = r.obtainAttributes(attrs,
    126                     com.android.internal.R.styleable.LayerDrawableItem);
    127 
    128             int left = a.getDimensionPixelOffset(
    129                     com.android.internal.R.styleable.LayerDrawableItem_left, 0);
    130             int top = a.getDimensionPixelOffset(
    131                     com.android.internal.R.styleable.LayerDrawableItem_top, 0);
    132             int right = a.getDimensionPixelOffset(
    133                     com.android.internal.R.styleable.LayerDrawableItem_right, 0);
    134             int bottom = a.getDimensionPixelOffset(
    135                     com.android.internal.R.styleable.LayerDrawableItem_bottom, 0);
    136             int drawableRes = a.getResourceId(
    137                     com.android.internal.R.styleable.LayerDrawableItem_drawable, 0);
    138             int id = a.getResourceId(com.android.internal.R.styleable.LayerDrawableItem_id,
    139                     View.NO_ID);
    140 
    141             a.recycle();
    142 
    143             Drawable dr;
    144             if (drawableRes != 0) {
    145                 dr = r.getDrawable(drawableRes);
    146             } else {
    147                 while ((type = parser.next()) == XmlPullParser.TEXT) {
    148                 }
    149                 if (type != XmlPullParser.START_TAG) {
    150                     throw new XmlPullParserException(parser.getPositionDescription()
    151                             + ": <item> tag requires a 'drawable' attribute or "
    152                             + "child tag defining a drawable");
    153                 }
    154                 dr = Drawable.createFromXmlInner(r, parser, attrs);
    155             }
    156 
    157             addLayer(dr, id, left, top, right, bottom);
    158         }
    159 
    160         ensurePadding();
    161         onStateChange(getState());
    162     }
    163 
    164     /**
    165      * Add a new layer to this drawable. The new layer is identified by an id.
    166      *
    167      * @param layer The drawable to add as a layer.
    168      * @param id The id of the new layer.
    169      * @param left The left padding of the new layer.
    170      * @param top The top padding of the new layer.
    171      * @param right The right padding of the new layer.
    172      * @param bottom The bottom padding of the new layer.
    173      */
    174     private void addLayer(Drawable layer, int id, int left, int top, int right, int bottom) {
    175         final LayerState st = mLayerState;
    176         int N = st.mChildren != null ? st.mChildren.length : 0;
    177         int i = st.mNum;
    178         if (i >= N) {
    179             ChildDrawable[] nu = new ChildDrawable[N + 10];
    180             if (i > 0) {
    181                 System.arraycopy(st.mChildren, 0, nu, 0, i);
    182             }
    183             st.mChildren = nu;
    184         }
    185 
    186         mLayerState.mChildrenChangingConfigurations |= layer.getChangingConfigurations();
    187 
    188         ChildDrawable childDrawable = new ChildDrawable();
    189         st.mChildren[i] = childDrawable;
    190         childDrawable.mId = id;
    191         childDrawable.mDrawable = layer;
    192         childDrawable.mInsetL = left;
    193         childDrawable.mInsetT = top;
    194         childDrawable.mInsetR = right;
    195         childDrawable.mInsetB = bottom;
    196         st.mNum++;
    197 
    198         layer.setCallback(this);
    199     }
    200 
    201     /**
    202      * Look for a layer with the given id, and returns its {@link Drawable}.
    203      *
    204      * @param id The layer ID to search for.
    205      * @return The {@link Drawable} of the layer that has the given id in the hierarchy or null.
    206      */
    207     public Drawable findDrawableByLayerId(int id) {
    208         final ChildDrawable[] layers = mLayerState.mChildren;
    209 
    210         for (int i = mLayerState.mNum - 1; i >= 0; i--) {
    211             if (layers[i].mId == id) {
    212                 return layers[i].mDrawable;
    213             }
    214         }
    215 
    216         return null;
    217     }
    218 
    219     /**
    220      * Sets the ID of a layer.
    221      *
    222      * @param index The index of the layer which will received the ID.
    223      * @param id The ID to assign to the layer.
    224      */
    225     public void setId(int index, int id) {
    226         mLayerState.mChildren[index].mId = id;
    227     }
    228 
    229     /**
    230      * Returns the number of layers contained within this.
    231      * @return The number of layers.
    232      */
    233     public int getNumberOfLayers() {
    234         return mLayerState.mNum;
    235     }
    236 
    237     /**
    238      * Returns the drawable at the specified layer index.
    239      *
    240      * @param index The layer index of the drawable to retrieve.
    241      *
    242      * @return The {@link android.graphics.drawable.Drawable} at the specified layer index.
    243      */
    244     public Drawable getDrawable(int index) {
    245         return mLayerState.mChildren[index].mDrawable;
    246     }
    247 
    248     /**
    249      * Returns the id of the specified layer.
    250      *
    251      * @param index The index of the layer.
    252      *
    253      * @return The id of the layer or {@link android.view.View#NO_ID} if the layer has no id.
    254      */
    255     public int getId(int index) {
    256         return mLayerState.mChildren[index].mId;
    257     }
    258 
    259     /**
    260      * Sets (or replaces) the {@link Drawable} for the layer with the given id.
    261      *
    262      * @param id The layer ID to search for.
    263      * @param drawable The replacement {@link Drawable}.
    264      * @return Whether the {@link Drawable} was replaced (could return false if
    265      *         the id was not found).
    266      */
    267     public boolean setDrawableByLayerId(int id, Drawable drawable) {
    268         final ChildDrawable[] layers = mLayerState.mChildren;
    269 
    270         for (int i = mLayerState.mNum - 1; i >= 0; i--) {
    271             if (layers[i].mId == id) {
    272                 layers[i].mDrawable = drawable;
    273                 return true;
    274             }
    275         }
    276 
    277         return false;
    278     }
    279 
    280     /** Specify modifiers to the bounds for the drawable[index].
    281         left += l
    282         top += t;
    283         right -= r;
    284         bottom -= b;
    285     */
    286     public void setLayerInset(int index, int l, int t, int r, int b) {
    287         ChildDrawable childDrawable = mLayerState.mChildren[index];
    288         childDrawable.mInsetL = l;
    289         childDrawable.mInsetT = t;
    290         childDrawable.mInsetR = r;
    291         childDrawable.mInsetB = b;
    292     }
    293 
    294     // overrides from Drawable.Callback
    295 
    296     public void invalidateDrawable(Drawable who) {
    297         if (mCallback != null) {
    298             mCallback.invalidateDrawable(this);
    299         }
    300     }
    301 
    302     public void scheduleDrawable(Drawable who, Runnable what, long when) {
    303         if (mCallback != null) {
    304             mCallback.scheduleDrawable(this, what, when);
    305         }
    306     }
    307 
    308     public void unscheduleDrawable(Drawable who, Runnable what) {
    309         if (mCallback != null) {
    310             mCallback.unscheduleDrawable(this, what);
    311         }
    312     }
    313 
    314     // overrides from Drawable
    315 
    316     @Override
    317     public void draw(Canvas canvas) {
    318         final ChildDrawable[] array = mLayerState.mChildren;
    319         final int N = mLayerState.mNum;
    320         for (int i=0; i<N; i++) {
    321             array[i].mDrawable.draw(canvas);
    322         }
    323     }
    324 
    325     @Override
    326     public int getChangingConfigurations() {
    327         return super.getChangingConfigurations()
    328                 | mLayerState.mChangingConfigurations
    329                 | mLayerState.mChildrenChangingConfigurations;
    330     }
    331 
    332     @Override
    333     public boolean getPadding(Rect padding) {
    334         // Arbitrarily get the padding from the first image.
    335         // Technically we should maybe do something more intelligent,
    336         // like take the max padding of all the images.
    337         padding.left = 0;
    338         padding.top = 0;
    339         padding.right = 0;
    340         padding.bottom = 0;
    341         final ChildDrawable[] array = mLayerState.mChildren;
    342         final int N = mLayerState.mNum;
    343         for (int i=0; i<N; i++) {
    344             reapplyPadding(i, array[i]);
    345             padding.left += mPaddingL[i];
    346             padding.top += mPaddingT[i];
    347             padding.right += mPaddingR[i];
    348             padding.bottom += mPaddingB[i];
    349         }
    350         return true;
    351     }
    352 
    353     @Override
    354     public boolean setVisible(boolean visible, boolean restart) {
    355         boolean changed = super.setVisible(visible, restart);
    356         final ChildDrawable[] array = mLayerState.mChildren;
    357         final int N = mLayerState.mNum;
    358         for (int i=0; i<N; i++) {
    359             array[i].mDrawable.setVisible(visible, restart);
    360         }
    361         return changed;
    362     }
    363 
    364     @Override
    365     public void setDither(boolean dither) {
    366         final ChildDrawable[] array = mLayerState.mChildren;
    367         final int N = mLayerState.mNum;
    368         for (int i=0; i<N; i++) {
    369             array[i].mDrawable.setDither(dither);
    370         }
    371     }
    372 
    373     @Override
    374     public void setAlpha(int alpha) {
    375         final ChildDrawable[] array = mLayerState.mChildren;
    376         final int N = mLayerState.mNum;
    377         for (int i=0; i<N; i++) {
    378             array[i].mDrawable.setAlpha(alpha);
    379         }
    380     }
    381 
    382     @Override
    383     public void setColorFilter(ColorFilter cf) {
    384         final ChildDrawable[] array = mLayerState.mChildren;
    385         final int N = mLayerState.mNum;
    386         for (int i=0; i<N; i++) {
    387             array[i].mDrawable.setColorFilter(cf);
    388         }
    389     }
    390 
    391     @Override
    392     public int getOpacity() {
    393         return mLayerState.getOpacity();
    394     }
    395 
    396     @Override
    397     public boolean isStateful() {
    398         return mLayerState.isStateful();
    399     }
    400 
    401     @Override
    402     protected boolean onStateChange(int[] state) {
    403         final ChildDrawable[] array = mLayerState.mChildren;
    404         final int N = mLayerState.mNum;
    405         boolean paddingChanged = false;
    406         boolean changed = false;
    407         for (int i=0; i<N; i++) {
    408             final ChildDrawable r = array[i];
    409             if (r.mDrawable.setState(state)) {
    410                 changed = true;
    411             }
    412             if (reapplyPadding(i, r)) {
    413                 paddingChanged = true;
    414             }
    415         }
    416         if (paddingChanged) {
    417             onBoundsChange(getBounds());
    418         }
    419         return changed;
    420     }
    421 
    422     @Override
    423     protected boolean onLevelChange(int level) {
    424         final ChildDrawable[] array = mLayerState.mChildren;
    425         final int N = mLayerState.mNum;
    426         boolean paddingChanged = false;
    427         boolean changed = false;
    428         for (int i=0; i<N; i++) {
    429             final ChildDrawable r = array[i];
    430             if (r.mDrawable.setLevel(level)) {
    431                 changed = true;
    432             }
    433             if (reapplyPadding(i, r)) {
    434                 paddingChanged = true;
    435             }
    436         }
    437         if (paddingChanged) {
    438             onBoundsChange(getBounds());
    439         }
    440         return changed;
    441     }
    442 
    443     @Override
    444     protected void onBoundsChange(Rect bounds) {
    445         final ChildDrawable[] array = mLayerState.mChildren;
    446         final int N = mLayerState.mNum;
    447         int padL=0, padT=0, padR=0, padB=0;
    448         for (int i=0; i<N; i++) {
    449             final ChildDrawable r = array[i];
    450             r.mDrawable.setBounds(bounds.left + r.mInsetL + padL,
    451                                   bounds.top + r.mInsetT + padT,
    452                                   bounds.right - r.mInsetR - padR,
    453                                   bounds.bottom - r.mInsetB - padB);
    454             padL += mPaddingL[i];
    455             padR += mPaddingR[i];
    456             padT += mPaddingT[i];
    457             padB += mPaddingB[i];
    458         }
    459     }
    460 
    461     @Override
    462     public int getIntrinsicWidth() {
    463         int width = -1;
    464         final ChildDrawable[] array = mLayerState.mChildren;
    465         final int N = mLayerState.mNum;
    466         int padL=0, padR=0;
    467         for (int i=0; i<N; i++) {
    468             final ChildDrawable r = array[i];
    469             int w = r.mDrawable.getIntrinsicWidth()
    470                   + r.mInsetL + r.mInsetR + padL + padR;
    471             if (w > width) {
    472                 width = w;
    473             }
    474             padL += mPaddingL[i];
    475             padR += mPaddingR[i];
    476         }
    477         return width;
    478     }
    479 
    480     @Override
    481     public int getIntrinsicHeight() {
    482         int height = -1;
    483         final ChildDrawable[] array = mLayerState.mChildren;
    484         final int N = mLayerState.mNum;
    485         int padT=0, padB=0;
    486         for (int i=0; i<N; i++) {
    487             final ChildDrawable r = array[i];
    488             int h = r.mDrawable.getIntrinsicHeight() + r.mInsetT + r.mInsetB + + padT + padB;
    489             if (h > height) {
    490                 height = h;
    491             }
    492             padT += mPaddingT[i];
    493             padB += mPaddingB[i];
    494         }
    495         return height;
    496     }
    497 
    498     private boolean reapplyPadding(int i, ChildDrawable r) {
    499         final Rect rect = mTmpRect;
    500         r.mDrawable.getPadding(rect);
    501         if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] ||
    502                 rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) {
    503             mPaddingL[i] = rect.left;
    504             mPaddingT[i] = rect.top;
    505             mPaddingR[i] = rect.right;
    506             mPaddingB[i] = rect.bottom;
    507             return true;
    508         }
    509         return false;
    510     }
    511 
    512     private void ensurePadding() {
    513         final int N = mLayerState.mNum;
    514         if (mPaddingL != null && mPaddingL.length >= N) {
    515             return;
    516         }
    517         mPaddingL = new int[N];
    518         mPaddingT = new int[N];
    519         mPaddingR = new int[N];
    520         mPaddingB = new int[N];
    521     }
    522 
    523     @Override
    524     public ConstantState getConstantState() {
    525         if (mLayerState.canConstantState()) {
    526             mLayerState.mChangingConfigurations = super.getChangingConfigurations();
    527             return mLayerState;
    528         }
    529         return null;
    530     }
    531 
    532     @Override
    533     public Drawable mutate() {
    534         if (!mMutated && super.mutate() == this) {
    535             final ChildDrawable[] array = mLayerState.mChildren;
    536             final int N = mLayerState.mNum;
    537             for (int i = 0; i < N; i++) {
    538                 array[i].mDrawable.mutate();
    539             }
    540             mMutated = true;
    541         }
    542         return this;
    543     }
    544 
    545     static class ChildDrawable {
    546         public Drawable mDrawable;
    547         public int mInsetL, mInsetT, mInsetR, mInsetB;
    548         public int mId;
    549     }
    550 
    551     static class LayerState extends ConstantState {
    552         int mNum;
    553         ChildDrawable[] mChildren;
    554 
    555         int mChangingConfigurations;
    556         int mChildrenChangingConfigurations;
    557 
    558         private boolean mHaveOpacity = false;
    559         private int mOpacity;
    560 
    561         private boolean mHaveStateful = false;
    562         private boolean mStateful;
    563 
    564         private boolean mCheckedConstantState;
    565         private boolean mCanConstantState;
    566 
    567         LayerState(LayerState orig, LayerDrawable owner, Resources res) {
    568             if (orig != null) {
    569                 final ChildDrawable[] origChildDrawable = orig.mChildren;
    570                 final int N = orig.mNum;
    571 
    572                 mNum = N;
    573                 mChildren = new ChildDrawable[N];
    574 
    575                 mChangingConfigurations = orig.mChangingConfigurations;
    576                 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
    577 
    578                 for (int i = 0; i < N; i++) {
    579                     final ChildDrawable r = mChildren[i] = new ChildDrawable();
    580                     final ChildDrawable or = origChildDrawable[i];
    581                     if (res != null) {
    582                         r.mDrawable = or.mDrawable.getConstantState().newDrawable(res);
    583                     } else {
    584                         r.mDrawable = or.mDrawable.getConstantState().newDrawable();
    585                     }
    586                     r.mDrawable.setCallback(owner);
    587                     r.mInsetL = or.mInsetL;
    588                     r.mInsetT = or.mInsetT;
    589                     r.mInsetR = or.mInsetR;
    590                     r.mInsetB = or.mInsetB;
    591                     r.mId = or.mId;
    592                 }
    593 
    594                 mHaveOpacity = orig.mHaveOpacity;
    595                 mOpacity = orig.mOpacity;
    596                 mHaveStateful = orig.mHaveStateful;
    597                 mStateful = orig.mStateful;
    598                 mCheckedConstantState = mCanConstantState = true;
    599             } else {
    600                 mNum = 0;
    601                 mChildren = null;
    602             }
    603         }
    604 
    605         @Override
    606         public Drawable newDrawable() {
    607             return new LayerDrawable(this, null);
    608         }
    609 
    610         @Override
    611         public Drawable newDrawable(Resources res) {
    612             return new LayerDrawable(this, res);
    613         }
    614 
    615         @Override
    616         public int getChangingConfigurations() {
    617             return mChangingConfigurations;
    618         }
    619 
    620         public final int getOpacity() {
    621             if (mHaveOpacity) {
    622                 return mOpacity;
    623             }
    624 
    625             final int N = mNum;
    626             int op = N > 0 ? mChildren[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT;
    627             for (int i = 1; i < N; i++) {
    628                 op = Drawable.resolveOpacity(op, mChildren[i].mDrawable.getOpacity());
    629             }
    630             mOpacity = op;
    631             mHaveOpacity = true;
    632             return op;
    633         }
    634 
    635         public final boolean isStateful() {
    636             if (mHaveStateful) {
    637                 return mStateful;
    638             }
    639 
    640             boolean stateful = false;
    641             final int N = mNum;
    642             for (int i = 0; i < N; i++) {
    643                 if (mChildren[i].mDrawable.isStateful()) {
    644                     stateful = true;
    645                     break;
    646                 }
    647             }
    648 
    649             mStateful = stateful;
    650             mHaveStateful = true;
    651             return stateful;
    652         }
    653 
    654         public synchronized boolean canConstantState() {
    655             if (!mCheckedConstantState && mChildren != null) {
    656                 mCanConstantState = true;
    657                 final int N = mNum;
    658                 for (int i=0; i<N; i++) {
    659                     if (mChildren[i].mDrawable.getConstantState() == null) {
    660                         mCanConstantState = false;
    661                         break;
    662                     }
    663                 }
    664                 mCheckedConstantState = true;
    665             }
    666 
    667             return mCanConstantState;
    668         }
    669     }
    670 }
    671 
    672