• 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>. 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      */
LayerDrawable(Drawable[] layers)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      */
LayerDrawable(Drawable[] layers, LayerState state)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 
LayerDrawable()90     LayerDrawable() {
91         this((LayerState) null, null);
92     }
93 
LayerDrawable(LayerState state, Resources res)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 
createConstantState(LayerState state, Resources res)102     LayerState createConstantState(LayerState state, Resources res) {
103         return new LayerState(state, this, res);
104     }
105 
106     @Override
inflate(Resources r, XmlPullParser parser, AttributeSet attrs)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      */
addLayer(Drawable layer, int id, int left, int top, int right, int bottom)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      */
findDrawableByLayerId(int id)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      */
setId(int index, int id)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      */
getNumberOfLayers()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      */
getDrawable(int index)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      */
getId(int index)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      */
setDrawableByLayerId(int id, Drawable drawable)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     */
setLayerInset(int index, int l, int t, int r, int b)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 
invalidateDrawable(Drawable who)296     public void invalidateDrawable(Drawable who) {
297         if (mCallback != null) {
298             mCallback.invalidateDrawable(this);
299         }
300     }
301 
scheduleDrawable(Drawable who, Runnable what, long when)302     public void scheduleDrawable(Drawable who, Runnable what, long when) {
303         if (mCallback != null) {
304             mCallback.scheduleDrawable(this, what, when);
305         }
306     }
307 
unscheduleDrawable(Drawable who, Runnable what)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
draw(Canvas canvas)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
getChangingConfigurations()326     public int getChangingConfigurations() {
327         return super.getChangingConfigurations()
328                 | mLayerState.mChangingConfigurations
329                 | mLayerState.mChildrenChangingConfigurations;
330     }
331 
332     @Override
getPadding(Rect padding)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
setVisible(boolean visible, boolean restart)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
setDither(boolean dither)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
setAlpha(int alpha)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
setColorFilter(ColorFilter cf)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
getOpacity()392     public int getOpacity() {
393         return mLayerState.getOpacity();
394     }
395 
396     @Override
isStateful()397     public boolean isStateful() {
398         return mLayerState.isStateful();
399     }
400 
401     @Override
onStateChange(int[] state)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
onLevelChange(int level)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
onBoundsChange(Rect bounds)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
getIntrinsicWidth()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
getIntrinsicHeight()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 
reapplyPadding(int i, ChildDrawable r)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 
ensurePadding()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
getConstantState()524     public ConstantState getConstantState() {
525         if (mLayerState.canConstantState()) {
526             mLayerState.mChangingConfigurations = super.getChangingConfigurations();
527             return mLayerState;
528         }
529         return null;
530     }
531 
532     @Override
mutate()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 
LayerState(LayerState orig, LayerDrawable owner, Resources res)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
newDrawable()606         public Drawable newDrawable() {
607             return new LayerDrawable(this, null);
608         }
609 
610         @Override
newDrawable(Resources res)611         public Drawable newDrawable(Resources res) {
612             return new LayerDrawable(this, res);
613         }
614 
615         @Override
getChangingConfigurations()616         public int getChangingConfigurations() {
617             return mChangingConfigurations;
618         }
619 
getOpacity()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 
isStateful()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 
canConstantState()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