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