InsetDrawable.java revision c054966b719c8a7255f7cf4120cca5050acb68bd
1/*
2 * Copyright (C) 2008 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 android.annotation.NonNull;
20import org.xmlpull.v1.XmlPullParser;
21import org.xmlpull.v1.XmlPullParserException;
22
23import android.content.res.ColorStateList;
24import android.content.res.Resources;
25import android.content.res.TypedArray;
26import android.content.res.Resources.Theme;
27import android.graphics.*;
28import android.graphics.PorterDuff.Mode;
29import android.util.AttributeSet;
30import android.util.Log;
31
32import java.io.IOException;
33
34/**
35 * A Drawable that insets another Drawable by a specified distance.
36 * This is used when a View needs a background that is smaller than
37 * the View's actual bounds.
38 *
39 * <p>It can be defined in an XML file with the <code>&lt;inset></code> element. For more
40 * information, see the guide to <a
41 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
42 *
43 * @attr ref android.R.styleable#InsetDrawable_visible
44 * @attr ref android.R.styleable#InsetDrawable_drawable
45 * @attr ref android.R.styleable#InsetDrawable_insetLeft
46 * @attr ref android.R.styleable#InsetDrawable_insetRight
47 * @attr ref android.R.styleable#InsetDrawable_insetTop
48 * @attr ref android.R.styleable#InsetDrawable_insetBottom
49 */
50public class InsetDrawable extends Drawable implements Drawable.Callback {
51    // Most of this is copied from ScaleDrawable.
52    private InsetState mInsetState;
53    private final Rect mTmpRect = new Rect();
54    private boolean mMutated;
55
56    /*package*/ InsetDrawable() {
57        this(null, null);
58    }
59
60    public InsetDrawable(Drawable drawable, int inset) {
61        this(drawable, inset, inset, inset, inset);
62    }
63
64    public InsetDrawable(Drawable drawable, int insetLeft, int insetTop,
65                         int insetRight, int insetBottom) {
66        this(null, null);
67
68        mInsetState.mDrawable = drawable;
69        mInsetState.mInsetLeft = insetLeft;
70        mInsetState.mInsetTop = insetTop;
71        mInsetState.mInsetRight = insetRight;
72        mInsetState.mInsetBottom = insetBottom;
73
74        if (drawable != null) {
75            drawable.setCallback(this);
76        }
77    }
78
79    @Override
80    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
81            throws XmlPullParserException, IOException {
82        int type;
83
84        TypedArray a = r.obtainAttributes(attrs,
85                com.android.internal.R.styleable.InsetDrawable);
86
87        super.inflateWithAttributes(r, parser, a,
88                com.android.internal.R.styleable.InsetDrawable_visible);
89
90        int drawableRes = a.getResourceId(com.android.internal.R.styleable.
91                                    InsetDrawable_drawable, 0);
92
93        int inLeft = a.getDimensionPixelOffset(com.android.internal.R.styleable.
94                                    InsetDrawable_insetLeft, 0);
95        int inTop = a.getDimensionPixelOffset(com.android.internal.R.styleable.
96                                    InsetDrawable_insetTop, 0);
97        int inRight = a.getDimensionPixelOffset(com.android.internal.R.styleable.
98                                    InsetDrawable_insetRight, 0);
99        int inBottom = a.getDimensionPixelOffset(com.android.internal.R.styleable.
100                                    InsetDrawable_insetBottom, 0);
101
102        a.recycle();
103
104        Drawable dr;
105        if (drawableRes != 0) {
106            dr = r.getDrawable(drawableRes);
107        } else {
108            while ((type=parser.next()) == XmlPullParser.TEXT) {
109            }
110            if (type != XmlPullParser.START_TAG) {
111                throw new XmlPullParserException(
112                        parser.getPositionDescription()
113                        + ": <inset> tag requires a 'drawable' attribute or "
114                        + "child tag defining a drawable");
115            }
116            dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
117        }
118
119        if (dr == null) {
120            Log.w("drawable", "No drawable specified for <inset>");
121        }
122
123        mInsetState.mDrawable = dr;
124        mInsetState.mInsetLeft = inLeft;
125        mInsetState.mInsetRight = inRight;
126        mInsetState.mInsetTop = inTop;
127        mInsetState.mInsetBottom = inBottom;
128
129        if (dr != null) {
130            dr.setCallback(this);
131        }
132    }
133
134    // overrides from Drawable.Callback
135
136    @Override
137    public void invalidateDrawable(Drawable who) {
138        final Callback callback = getCallback();
139        if (callback != null) {
140            callback.invalidateDrawable(this);
141        }
142    }
143
144    @Override
145    public void scheduleDrawable(Drawable who, Runnable what, long when) {
146        final Callback callback = getCallback();
147        if (callback != null) {
148            callback.scheduleDrawable(this, what, when);
149        }
150    }
151
152    @Override
153    public void unscheduleDrawable(Drawable who, Runnable what) {
154        final Callback callback = getCallback();
155        if (callback != null) {
156            callback.unscheduleDrawable(this, what);
157        }
158    }
159
160    // overrides from Drawable
161
162    @Override
163    public void draw(Canvas canvas) {
164        mInsetState.mDrawable.draw(canvas);
165    }
166
167    @Override
168    public int getChangingConfigurations() {
169        return super.getChangingConfigurations()
170                | mInsetState.mChangingConfigurations
171                | mInsetState.mDrawable.getChangingConfigurations();
172    }
173
174    @Override
175    public boolean getPadding(Rect padding) {
176        boolean pad = mInsetState.mDrawable.getPadding(padding);
177
178        padding.left += mInsetState.mInsetLeft;
179        padding.right += mInsetState.mInsetRight;
180        padding.top += mInsetState.mInsetTop;
181        padding.bottom += mInsetState.mInsetBottom;
182
183        if (pad || (mInsetState.mInsetLeft | mInsetState.mInsetRight |
184                    mInsetState.mInsetTop | mInsetState.mInsetBottom) != 0) {
185            return true;
186        } else {
187            return false;
188        }
189    }
190
191    /** @hide */
192    @Override
193    public Insets getOpticalInsets() {
194        final Insets contentInsets = super.getOpticalInsets();
195        return Insets.of(contentInsets.left + mInsetState.mInsetLeft,
196                contentInsets.top + mInsetState.mInsetTop,
197                contentInsets.right + mInsetState.mInsetRight,
198                contentInsets.bottom + mInsetState.mInsetBottom);
199    }
200
201    @Override
202    public void setHotspot(float x, float y) {
203        mInsetState.mDrawable.setHotspot(x, y);
204    }
205
206    @Override
207    public void setHotspotBounds(int left, int top, int right, int bottom) {
208        mInsetState.mDrawable.setHotspotBounds(left, top, right, bottom);
209    }
210
211    @Override
212    public boolean setVisible(boolean visible, boolean restart) {
213        mInsetState.mDrawable.setVisible(visible, restart);
214        return super.setVisible(visible, restart);
215    }
216
217    @Override
218    public void setAlpha(int alpha) {
219        mInsetState.mDrawable.setAlpha(alpha);
220    }
221
222    @Override
223    public int getAlpha() {
224        return mInsetState.mDrawable.getAlpha();
225    }
226
227    @Override
228    public void setColorFilter(ColorFilter cf) {
229        mInsetState.mDrawable.setColorFilter(cf);
230    }
231
232    @Override
233    public void setTint(ColorStateList tint, Mode tintMode) {
234        mInsetState.mDrawable.setTint(tint, tintMode);
235    }
236
237    /** {@hide} */
238    @Override
239    public void setLayoutDirection(int layoutDirection) {
240        mInsetState.mDrawable.setLayoutDirection(layoutDirection);
241    }
242
243    @Override
244    public int getOpacity() {
245        return mInsetState.mDrawable.getOpacity();
246    }
247
248    @Override
249    public boolean isStateful() {
250        return mInsetState.mDrawable.isStateful();
251    }
252
253    @Override
254    protected boolean onStateChange(int[] state) {
255        boolean changed = mInsetState.mDrawable.setState(state);
256        onBoundsChange(getBounds());
257        return changed;
258    }
259
260    @Override
261    protected boolean onLevelChange(int level) {
262        return mInsetState.mDrawable.setLevel(level);
263    }
264
265    @Override
266    protected void onBoundsChange(Rect bounds) {
267        final Rect r = mTmpRect;
268        r.set(bounds);
269
270        r.left += mInsetState.mInsetLeft;
271        r.top += mInsetState.mInsetTop;
272        r.right -= mInsetState.mInsetRight;
273        r.bottom -= mInsetState.mInsetBottom;
274
275        mInsetState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom);
276    }
277
278    @Override
279    public int getIntrinsicWidth() {
280        return mInsetState.mDrawable.getIntrinsicWidth();
281    }
282
283    @Override
284    public int getIntrinsicHeight() {
285        return mInsetState.mDrawable.getIntrinsicHeight();
286    }
287
288    @Override
289    public boolean getOutline(@NonNull Outline outline) {
290        return mInsetState.mDrawable.getOutline(outline);
291    }
292
293    @Override
294    public ConstantState getConstantState() {
295        if (mInsetState.canConstantState()) {
296            mInsetState.mChangingConfigurations = getChangingConfigurations();
297            return mInsetState;
298        }
299        return null;
300    }
301
302    @Override
303    public Drawable mutate() {
304        if (!mMutated && super.mutate() == this) {
305            mInsetState.mDrawable.mutate();
306            mMutated = true;
307        }
308        return this;
309    }
310
311    /**
312     * Returns the drawable wrapped by this InsetDrawable. May be null.
313     */
314    public Drawable getDrawable() {
315        return mInsetState.mDrawable;
316    }
317
318    final static class InsetState extends ConstantState {
319        Drawable mDrawable;
320        int mChangingConfigurations;
321
322        int mInsetLeft;
323        int mInsetTop;
324        int mInsetRight;
325        int mInsetBottom;
326
327        boolean mCheckedConstantState;
328        boolean mCanConstantState;
329
330        InsetState(InsetState orig, InsetDrawable owner, Resources res) {
331            if (orig != null) {
332                if (res != null) {
333                    mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
334                } else {
335                    mDrawable = orig.mDrawable.getConstantState().newDrawable();
336                }
337                mDrawable.setCallback(owner);
338                mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection());
339                mInsetLeft = orig.mInsetLeft;
340                mInsetTop = orig.mInsetTop;
341                mInsetRight = orig.mInsetRight;
342                mInsetBottom = orig.mInsetBottom;
343                mCheckedConstantState = mCanConstantState = true;
344            }
345        }
346
347        @Override
348        public Drawable newDrawable() {
349            return new InsetDrawable(this, null);
350        }
351
352        @Override
353        public Drawable newDrawable(Resources res) {
354            return new InsetDrawable(this, res);
355        }
356
357        @Override
358        public int getChangingConfigurations() {
359            return mChangingConfigurations;
360        }
361
362        boolean canConstantState() {
363            if (!mCheckedConstantState) {
364                mCanConstantState = mDrawable.getConstantState() != null;
365                mCheckedConstantState = true;
366            }
367
368            return mCanConstantState;
369        }
370    }
371
372    private InsetDrawable(InsetState state, Resources res) {
373        mInsetState = new InsetState(state, this, res);
374    }
375}
376
377