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