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