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