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