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