InsetDrawable.java revision a426445dfdab43886dd894f2ba8a1d55bfcbb278
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 Drawable dr = a.getDrawable(R.styleable.InsetDrawable_drawable);
123        if (dr != null) {
124            state.mDrawable = dr;
125            dr.setCallback(this);
126        }
127
128        state.mInsetLeft = a.getDimensionPixelOffset(
129                R.styleable.InsetDrawable_insetLeft, state.mInsetLeft);
130        state.mInsetTop = a.getDimensionPixelOffset(
131                R.styleable.InsetDrawable_insetTop, state.mInsetTop);
132        state.mInsetRight = a.getDimensionPixelOffset(
133                R.styleable.InsetDrawable_insetRight, state.mInsetRight);
134        state.mInsetBottom = a.getDimensionPixelOffset(
135                R.styleable.InsetDrawable_insetBottom, state.mInsetBottom);
136    }
137
138    @Override
139    public void applyTheme(Theme t) {
140        super.applyTheme(t);
141
142        final InsetState state = mInsetState;
143        if (state == null || state.mThemeAttrs == null) {
144            return;
145        }
146
147        final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable);
148        try {
149            updateStateFromTypedArray(a);
150        } catch (XmlPullParserException e) {
151            throw new RuntimeException(e);
152        } finally {
153            a.recycle();
154        }
155    }
156
157    @Override
158    public boolean canApplyTheme() {
159        return mInsetState != null && mInsetState.mThemeAttrs != null;
160    }
161
162    @Override
163    public void invalidateDrawable(Drawable who) {
164        final Callback callback = getCallback();
165        if (callback != null) {
166            callback.invalidateDrawable(this);
167        }
168    }
169
170    @Override
171    public void scheduleDrawable(Drawable who, Runnable what, long when) {
172        final Callback callback = getCallback();
173        if (callback != null) {
174            callback.scheduleDrawable(this, what, when);
175        }
176    }
177
178    @Override
179    public void unscheduleDrawable(Drawable who, Runnable what) {
180        final Callback callback = getCallback();
181        if (callback != null) {
182            callback.unscheduleDrawable(this, what);
183        }
184    }
185
186    @Override
187    public void draw(Canvas canvas) {
188        mInsetState.mDrawable.draw(canvas);
189    }
190
191    @Override
192    public int getChangingConfigurations() {
193        return super.getChangingConfigurations()
194                | mInsetState.mChangingConfigurations
195                | mInsetState.mDrawable.getChangingConfigurations();
196    }
197
198    @Override
199    public boolean getPadding(Rect padding) {
200        boolean pad = mInsetState.mDrawable.getPadding(padding);
201
202        padding.left += mInsetState.mInsetLeft;
203        padding.right += mInsetState.mInsetRight;
204        padding.top += mInsetState.mInsetTop;
205        padding.bottom += mInsetState.mInsetBottom;
206
207        if (pad || (mInsetState.mInsetLeft | mInsetState.mInsetRight |
208                    mInsetState.mInsetTop | mInsetState.mInsetBottom) != 0) {
209            return true;
210        } else {
211            return false;
212        }
213    }
214
215    /** @hide */
216    @Override
217    public Insets getOpticalInsets() {
218        final Insets contentInsets = super.getOpticalInsets();
219        return Insets.of(contentInsets.left + mInsetState.mInsetLeft,
220                contentInsets.top + mInsetState.mInsetTop,
221                contentInsets.right + mInsetState.mInsetRight,
222                contentInsets.bottom + mInsetState.mInsetBottom);
223    }
224
225    @Override
226    public void setHotspot(float x, float y) {
227        mInsetState.mDrawable.setHotspot(x, y);
228    }
229
230    @Override
231    public void setHotspotBounds(int left, int top, int right, int bottom) {
232        mInsetState.mDrawable.setHotspotBounds(left, top, right, bottom);
233    }
234
235    /** @hide */
236    @Override
237    public void getHotspotBounds(Rect outRect) {
238        mInsetState.mDrawable.getHotspotBounds(outRect);
239    }
240
241    @Override
242    public boolean setVisible(boolean visible, boolean restart) {
243        mInsetState.mDrawable.setVisible(visible, restart);
244        return super.setVisible(visible, restart);
245    }
246
247    @Override
248    public void setAlpha(int alpha) {
249        mInsetState.mDrawable.setAlpha(alpha);
250    }
251
252    @Override
253    public int getAlpha() {
254        return mInsetState.mDrawable.getAlpha();
255    }
256
257    @Override
258    public void setColorFilter(ColorFilter cf) {
259        mInsetState.mDrawable.setColorFilter(cf);
260    }
261
262    @Override
263    public void setTintList(ColorStateList tint) {
264        mInsetState.mDrawable.setTintList(tint);
265    }
266
267    @Override
268    public void setTintMode(Mode tintMode) {
269        mInsetState.mDrawable.setTintMode(tintMode);
270    }
271
272    /** {@hide} */
273    @Override
274    public void setLayoutDirection(int layoutDirection) {
275        mInsetState.mDrawable.setLayoutDirection(layoutDirection);
276    }
277
278    @Override
279    public int getOpacity() {
280        return mInsetState.mDrawable.getOpacity();
281    }
282
283    @Override
284    public boolean isStateful() {
285        return mInsetState.mDrawable.isStateful();
286    }
287
288    @Override
289    protected boolean onStateChange(int[] state) {
290        boolean changed = mInsetState.mDrawable.setState(state);
291        onBoundsChange(getBounds());
292        return changed;
293    }
294
295    @Override
296    protected boolean onLevelChange(int level) {
297        return mInsetState.mDrawable.setLevel(level);
298    }
299
300    @Override
301    protected void onBoundsChange(Rect bounds) {
302        final Rect r = mTmpRect;
303        r.set(bounds);
304
305        r.left += mInsetState.mInsetLeft;
306        r.top += mInsetState.mInsetTop;
307        r.right -= mInsetState.mInsetRight;
308        r.bottom -= mInsetState.mInsetBottom;
309
310        mInsetState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom);
311    }
312
313    @Override
314    public int getIntrinsicWidth() {
315        return mInsetState.mDrawable.getIntrinsicWidth();
316    }
317
318    @Override
319    public int getIntrinsicHeight() {
320        return mInsetState.mDrawable.getIntrinsicHeight();
321    }
322
323    @Override
324    public void getOutline(@NonNull Outline outline) {
325        mInsetState.mDrawable.getOutline(outline);
326    }
327
328    @Override
329    public ConstantState getConstantState() {
330        if (mInsetState.canConstantState()) {
331            mInsetState.mChangingConfigurations = getChangingConfigurations();
332            return mInsetState;
333        }
334        return null;
335    }
336
337    @Override
338    public Drawable mutate() {
339        if (!mMutated && super.mutate() == this) {
340            mInsetState.mDrawable.mutate();
341            mMutated = true;
342        }
343        return this;
344    }
345
346    /**
347     * Returns the drawable wrapped by this InsetDrawable. May be null.
348     */
349    public Drawable getDrawable() {
350        return mInsetState.mDrawable;
351    }
352
353    final static class InsetState extends ConstantState {
354        int[] mThemeAttrs;
355        int mChangingConfigurations;
356
357        Drawable mDrawable;
358
359        int mInsetLeft;
360        int mInsetTop;
361        int mInsetRight;
362        int mInsetBottom;
363
364        boolean mCheckedConstantState;
365        boolean mCanConstantState;
366
367        InsetState(InsetState orig, InsetDrawable owner, Resources res) {
368            if (orig != null) {
369                mThemeAttrs = orig.mThemeAttrs;
370                mChangingConfigurations = orig.mChangingConfigurations;
371                if (res != null) {
372                    mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
373                } else {
374                    mDrawable = orig.mDrawable.getConstantState().newDrawable();
375                }
376                mDrawable.setCallback(owner);
377                mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection());
378                mInsetLeft = orig.mInsetLeft;
379                mInsetTop = orig.mInsetTop;
380                mInsetRight = orig.mInsetRight;
381                mInsetBottom = orig.mInsetBottom;
382                mCheckedConstantState = mCanConstantState = true;
383            }
384        }
385
386        @Override
387        public Drawable newDrawable() {
388            return new InsetDrawable(this, null);
389        }
390
391        @Override
392        public Drawable newDrawable(Resources res) {
393            return new InsetDrawable(this, res);
394        }
395
396        @Override
397        public int getChangingConfigurations() {
398            return mChangingConfigurations;
399        }
400
401        boolean canConstantState() {
402            if (!mCheckedConstantState) {
403                mCanConstantState = mDrawable.getConstantState() != null;
404                mCheckedConstantState = true;
405            }
406
407            return mCanConstantState;
408        }
409    }
410
411    private InsetDrawable(InsetState state, Resources res) {
412        mInsetState = new InsetState(state, this, res);
413    }
414}
415
416