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