InsetDrawable.java revision 7068c39526459c18a020e29c1ebfa6aed54e2d0f
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 setTint(ColorStateList tint, Mode tintMode) {
264        mInsetState.mDrawable.setTint(tint, tintMode);
265    }
266
267    /** {@hide} */
268    @Override
269    public void setLayoutDirection(int layoutDirection) {
270        mInsetState.mDrawable.setLayoutDirection(layoutDirection);
271    }
272
273    @Override
274    public int getOpacity() {
275        return mInsetState.mDrawable.getOpacity();
276    }
277
278    @Override
279    public boolean isStateful() {
280        return mInsetState.mDrawable.isStateful();
281    }
282
283    @Override
284    protected boolean onStateChange(int[] state) {
285        boolean changed = mInsetState.mDrawable.setState(state);
286        onBoundsChange(getBounds());
287        return changed;
288    }
289
290    @Override
291    protected boolean onLevelChange(int level) {
292        return mInsetState.mDrawable.setLevel(level);
293    }
294
295    @Override
296    protected void onBoundsChange(Rect bounds) {
297        final Rect r = mTmpRect;
298        r.set(bounds);
299
300        r.left += mInsetState.mInsetLeft;
301        r.top += mInsetState.mInsetTop;
302        r.right -= mInsetState.mInsetRight;
303        r.bottom -= mInsetState.mInsetBottom;
304
305        mInsetState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom);
306    }
307
308    @Override
309    public int getIntrinsicWidth() {
310        return mInsetState.mDrawable.getIntrinsicWidth();
311    }
312
313    @Override
314    public int getIntrinsicHeight() {
315        return mInsetState.mDrawable.getIntrinsicHeight();
316    }
317
318    @Override
319    public boolean getOutline(@NonNull Outline outline) {
320        return mInsetState.mDrawable.getOutline(outline);
321    }
322
323    @Override
324    public ConstantState getConstantState() {
325        if (mInsetState.canConstantState()) {
326            mInsetState.mChangingConfigurations = getChangingConfigurations();
327            return mInsetState;
328        }
329        return null;
330    }
331
332    @Override
333    public Drawable mutate() {
334        if (!mMutated && super.mutate() == this) {
335            mInsetState.mDrawable.mutate();
336            mMutated = true;
337        }
338        return this;
339    }
340
341    /**
342     * Returns the drawable wrapped by this InsetDrawable. May be null.
343     */
344    public Drawable getDrawable() {
345        return mInsetState.mDrawable;
346    }
347
348    final static class InsetState extends ConstantState {
349        int[] mThemeAttrs;
350        int mChangingConfigurations;
351
352        Drawable mDrawable;
353
354        int mInsetLeft;
355        int mInsetTop;
356        int mInsetRight;
357        int mInsetBottom;
358
359        boolean mCheckedConstantState;
360        boolean mCanConstantState;
361
362        InsetState(InsetState orig, InsetDrawable owner, Resources res) {
363            if (orig != null) {
364                mThemeAttrs = orig.mThemeAttrs;
365                mChangingConfigurations = orig.mChangingConfigurations;
366                if (res != null) {
367                    mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
368                } else {
369                    mDrawable = orig.mDrawable.getConstantState().newDrawable();
370                }
371                mDrawable.setCallback(owner);
372                mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection());
373                mInsetLeft = orig.mInsetLeft;
374                mInsetTop = orig.mInsetTop;
375                mInsetRight = orig.mInsetRight;
376                mInsetBottom = orig.mInsetBottom;
377                mCheckedConstantState = mCanConstantState = true;
378            }
379        }
380
381        @Override
382        public Drawable newDrawable() {
383            return new InsetDrawable(this, null);
384        }
385
386        @Override
387        public Drawable newDrawable(Resources res) {
388            return new InsetDrawable(this, res);
389        }
390
391        @Override
392        public int getChangingConfigurations() {
393            return mChangingConfigurations;
394        }
395
396        boolean canConstantState() {
397            if (!mCheckedConstantState) {
398                mCanConstantState = mDrawable.getConstantState() != null;
399                mCheckedConstantState = true;
400            }
401
402            return mCanConstantState;
403        }
404    }
405
406    private InsetDrawable(InsetState state, Resources res) {
407        mInsetState = new InsetState(state, this, res);
408    }
409}
410
411