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