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