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