LayerDrawable.java revision d24b8183b93e781080b2c16c487e60d51c12da31
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
17package android.graphics.drawable;
18
19import org.xmlpull.v1.XmlPullParser;
20import org.xmlpull.v1.XmlPullParserException;
21
22import android.content.res.Resources;
23import android.content.res.TypedArray;
24import android.graphics.*;
25import android.util.AttributeSet;
26import android.view.View;
27
28import 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*/
45public 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     */
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     */
72    LayerDrawable(Drawable[] layers, LayerState state) {
73        this(state);
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
89    LayerDrawable() {
90        this((LayerState) null);
91    }
92
93    LayerDrawable(LayerState state) {
94        LayerState as = createConstantState(state);
95        mLayerState = as;
96        if (as.mNum > 0) {
97            ensurePadding();
98        }
99    }
100
101    LayerState createConstantState(LayerState state) {
102        return new LayerState(state, this);
103    }
104
105    @Override
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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    */
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
295    public void invalidateDrawable(Drawable who) {
296        if (mCallback != null) {
297            mCallback.invalidateDrawable(this);
298        }
299    }
300
301    public void scheduleDrawable(Drawable who, Runnable what, long when) {
302        if (mCallback != null) {
303            mCallback.scheduleDrawable(this, what, when);
304        }
305    }
306
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
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
325    public int getChangingConfigurations() {
326        return super.getChangingConfigurations()
327                | mLayerState.mChangingConfigurations
328                | mLayerState.mChildrenChangingConfigurations;
329    }
330
331    @Override
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
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
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
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
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
391    public int getOpacity() {
392        return mLayerState.getOpacity();
393    }
394
395    @Override
396    public boolean isStateful() {
397        return mLayerState.isStateful();
398    }
399
400    @Override
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
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
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
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
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
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
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
523    public ConstantState getConstantState() {
524        if (mLayerState.canConstantState()) {
525            mLayerState.mChangingConfigurations = super.getChangingConfigurations();
526            return mLayerState;
527        }
528        return null;
529    }
530
531    @Override
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
566        LayerState(LayerState orig, LayerDrawable owner) {
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                    r.mDrawable = or.mDrawable.getConstantState().newDrawable();
581                    r.mDrawable.setCallback(owner);
582                    r.mInsetL = or.mInsetL;
583                    r.mInsetT = or.mInsetT;
584                    r.mInsetR = or.mInsetR;
585                    r.mInsetB = or.mInsetB;
586                    r.mId = or.mId;
587                }
588
589                mHaveOpacity = orig.mHaveOpacity;
590                mOpacity = orig.mOpacity;
591                mHaveStateful = orig.mHaveStateful;
592                mStateful = orig.mStateful;
593                mCheckedConstantState = mCanConstantState = true;
594            } else {
595                mNum = 0;
596                mChildren = null;
597            }
598        }
599
600        @Override
601        public Drawable newDrawable() {
602            return new LayerDrawable(this);
603        }
604
605        @Override
606        public int getChangingConfigurations() {
607            return mChangingConfigurations;
608        }
609
610        public final int getOpacity() {
611            if (mHaveOpacity) {
612                return mOpacity;
613            }
614
615            final int N = mNum;
616            int op = N > 0 ? mChildren[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT;
617            for (int i = 1; i < N; i++) {
618                op = Drawable.resolveOpacity(op, mChildren[i].mDrawable.getOpacity());
619            }
620            mOpacity = op;
621            mHaveOpacity = true;
622            return op;
623        }
624
625        public final boolean isStateful() {
626            if (mHaveStateful) {
627                return mStateful;
628            }
629
630            boolean stateful = false;
631            final int N = mNum;
632            for (int i = 0; i < N; i++) {
633                if (mChildren[i].mDrawable.isStateful()) {
634                    stateful = true;
635                    break;
636                }
637            }
638
639            mStateful = stateful;
640            mHaveStateful = true;
641            return stateful;
642        }
643
644        public synchronized boolean canConstantState() {
645            if (!mCheckedConstantState && mChildren != null) {
646                mCanConstantState = true;
647                final int N = mNum;
648                for (int i=0; i<N; i++) {
649                    if (mChildren[i].mDrawable.getConstantState() == null) {
650                        mCanConstantState = false;
651                        break;
652                    }
653                }
654                mCheckedConstantState = true;
655            }
656
657            return mCanConstantState;
658        }
659    }
660}
661
662