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