ClipDrawable.java revision 7f4a63d1ebc13c6499a48331ecb78c4d27446dbc
1/*
2 * Copyright (C) 2006 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 org.xmlpull.v1.XmlPullParser;
22import org.xmlpull.v1.XmlPullParserException;
23
24import android.content.res.ColorStateList;
25import android.content.res.Resources;
26import android.content.res.TypedArray;
27import android.content.res.Resources.Theme;
28import android.graphics.*;
29import android.graphics.PorterDuff.Mode;
30import android.view.Gravity;
31import android.util.AttributeSet;
32
33import java.io.IOException;
34
35/**
36 * A Drawable that clips another Drawable based on this Drawable's current
37 * level value.  You can control how much the child Drawable gets clipped in width
38 * and height based on the level, as well as a gravity to control where it is
39 * placed in its overall container.  Most often used to implement things like
40 * progress bars, by increasing the drawable's level with {@link
41 * android.graphics.drawable.Drawable#setLevel(int) setLevel()}.
42 * <p class="note"><strong>Note:</strong> The drawable is clipped completely and not visible when
43 * the level is 0 and fully revealed when the level is 10,000.</p>
44 *
45 * <p>It can be defined in an XML file with the <code>&lt;clip></code> element.  For more
46 * information, see the guide to <a
47 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
48 *
49 * @attr ref android.R.styleable#ClipDrawable_clipOrientation
50 * @attr ref android.R.styleable#ClipDrawable_gravity
51 * @attr ref android.R.styleable#ClipDrawable_drawable
52 */
53public class ClipDrawable extends Drawable implements Drawable.Callback {
54    private ClipState mState;
55    private final Rect mTmpRect = new Rect();
56
57    public static final int HORIZONTAL = 1;
58    public static final int VERTICAL = 2;
59
60    private boolean mMutated;
61
62    ClipDrawable() {
63        this(null, null);
64    }
65
66    /**
67     * @param orientation Bitwise-or of {@link #HORIZONTAL} and/or {@link #VERTICAL}
68     */
69    public ClipDrawable(Drawable drawable, int gravity, int orientation) {
70        this(null, null);
71
72        mState.mDrawable = drawable;
73        mState.mGravity = gravity;
74        mState.mOrientation = orientation;
75
76        if (drawable != null) {
77            drawable.setCallback(this);
78        }
79    }
80
81    @Override
82    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
83            throws XmlPullParserException, IOException {
84        super.inflate(r, parser, attrs, theme);
85
86        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable);
87
88        // Reset mDrawable to preserve old multiple-inflate behavior. This is
89        // silly, but we have CTS tests that rely on it.
90        mState.mDrawable = null;
91
92        updateStateFromTypedArray(a);
93        inflateChildElements(r, parser, attrs, theme);
94        verifyRequiredAttributes(a);
95        a.recycle();
96    }
97
98    private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
99            Theme theme) throws XmlPullParserException, IOException {
100        Drawable dr = null;
101        int type;
102        final int outerDepth = parser.getDepth();
103        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
104                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
105            if (type != XmlPullParser.START_TAG) {
106                continue;
107            }
108            dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
109        }
110
111        if (dr != null) {
112            mState.mDrawable = dr;
113            dr.setCallback(this);
114        }
115    }
116
117    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
118        // If we're not waiting on a theme, verify required attributes.
119        if (mState.mDrawable == null && (mState.mThemeAttrs == null
120                || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) {
121            throw new XmlPullParserException(a.getPositionDescription()
122                    + ": <clip> tag requires a 'drawable' attribute or "
123                    + "child tag defining a drawable");
124        }
125    }
126
127    private void updateStateFromTypedArray(TypedArray a) {
128        final ClipState state = mState;
129
130        // Account for any configuration changes.
131        state.mChangingConfigurations |= a.getChangingConfigurations();
132
133        // Extract the theme attributes, if any.
134        state.mThemeAttrs = a.extractThemeAttrs();
135
136        state.mOrientation = a.getInt(R.styleable.ClipDrawable_clipOrientation, state.mOrientation);
137        state.mGravity = a.getInt(R.styleable.ClipDrawable_gravity, state.mGravity);
138
139        final Drawable dr = a.getDrawable(R.styleable.ClipDrawable_drawable);
140        if (dr != null) {
141            state.mDrawable = dr;
142            dr.setCallback(this);
143        }
144    }
145
146    @Override
147    public void applyTheme(Theme t) {
148        super.applyTheme(t);
149
150        final ClipState state = mState;
151        if (state == null || state.mThemeAttrs == null) {
152            return;
153        }
154
155        final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ClipDrawable);
156        try {
157            updateStateFromTypedArray(a);
158            verifyRequiredAttributes(a);
159        } catch (XmlPullParserException e) {
160            throw new RuntimeException(e);
161        } finally {
162            a.recycle();
163        }
164
165        if (state.mDrawable != null && state.mDrawable.canApplyTheme()) {
166            state.mDrawable.applyTheme(t);
167        }
168    }
169
170    @Override
171    public boolean canApplyTheme() {
172        return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
173    }
174
175    // overrides from Drawable.Callback
176
177    @Override
178    public void invalidateDrawable(Drawable who) {
179        final Callback callback = getCallback();
180        if (callback != null) {
181            callback.invalidateDrawable(this);
182        }
183    }
184
185    @Override
186    public void scheduleDrawable(Drawable who, Runnable what, long when) {
187        final Callback callback = getCallback();
188        if (callback != null) {
189            callback.scheduleDrawable(this, what, when);
190        }
191    }
192
193    @Override
194    public void unscheduleDrawable(Drawable who, Runnable what) {
195        final Callback callback = getCallback();
196        if (callback != null) {
197            callback.unscheduleDrawable(this, what);
198        }
199    }
200
201    // overrides from Drawable
202
203    @Override
204    public int getChangingConfigurations() {
205        return super.getChangingConfigurations()
206                | mState.mChangingConfigurations
207                | mState.mDrawable.getChangingConfigurations();
208    }
209
210    @Override
211    public boolean getPadding(Rect padding) {
212        // XXX need to adjust padding!
213        return mState.mDrawable.getPadding(padding);
214    }
215
216    @Override
217    public boolean setVisible(boolean visible, boolean restart) {
218        mState.mDrawable.setVisible(visible, restart);
219        return super.setVisible(visible, restart);
220    }
221
222    @Override
223    public void setAlpha(int alpha) {
224        mState.mDrawable.setAlpha(alpha);
225    }
226
227    @Override
228    public int getAlpha() {
229        return mState.mDrawable.getAlpha();
230    }
231
232    @Override
233    public void setColorFilter(ColorFilter cf) {
234        mState.mDrawable.setColorFilter(cf);
235    }
236
237    @Override
238    public void setTintList(ColorStateList tint) {
239        mState.mDrawable.setTintList(tint);
240    }
241
242    @Override
243    public void setTintMode(Mode tintMode) {
244        mState.mDrawable.setTintMode(tintMode);
245    }
246
247    @Override
248    public int getOpacity() {
249        return mState.mDrawable.getOpacity();
250    }
251
252    @Override
253    public boolean isStateful() {
254        return mState.mDrawable.isStateful();
255    }
256
257    @Override
258    protected boolean onStateChange(int[] state) {
259        return mState.mDrawable.setState(state);
260    }
261
262    @Override
263    protected boolean onLevelChange(int level) {
264        mState.mDrawable.setLevel(level);
265        invalidateSelf();
266        return true;
267    }
268
269    @Override
270    protected void onBoundsChange(Rect bounds) {
271        mState.mDrawable.setBounds(bounds);
272    }
273
274    @Override
275    public void draw(Canvas canvas) {
276
277        if (mState.mDrawable.getLevel() == 0) {
278            return;
279        }
280
281        final Rect r = mTmpRect;
282        final Rect bounds = getBounds();
283        int level = getLevel();
284        int w = bounds.width();
285        final int iw = 0; //mState.mDrawable.getIntrinsicWidth();
286        if ((mState.mOrientation & HORIZONTAL) != 0) {
287            w -= (w - iw) * (10000 - level) / 10000;
288        }
289        int h = bounds.height();
290        final int ih = 0; //mState.mDrawable.getIntrinsicHeight();
291        if ((mState.mOrientation & VERTICAL) != 0) {
292            h -= (h - ih) * (10000 - level) / 10000;
293        }
294        final int layoutDirection = getLayoutDirection();
295        Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection);
296
297        if (w > 0 && h > 0) {
298            canvas.save();
299            canvas.clipRect(r);
300            mState.mDrawable.draw(canvas);
301            canvas.restore();
302        }
303    }
304
305    @Override
306    public int getIntrinsicWidth() {
307        return mState.mDrawable.getIntrinsicWidth();
308    }
309
310    @Override
311    public int getIntrinsicHeight() {
312        return mState.mDrawable.getIntrinsicHeight();
313    }
314
315    @Override
316    public ConstantState getConstantState() {
317        if (mState.canConstantState()) {
318            mState.mChangingConfigurations = getChangingConfigurations();
319            return mState;
320        }
321        return null;
322    }
323
324    /** @hide */
325    @Override
326    public void setLayoutDirection(int layoutDirection) {
327        mState.mDrawable.setLayoutDirection(layoutDirection);
328        super.setLayoutDirection(layoutDirection);
329    }
330
331    @Override
332    public Drawable mutate() {
333        if (!mMutated && super.mutate() == this) {
334            mState.mDrawable.mutate();
335            mMutated = true;
336        }
337        return this;
338    }
339
340    /**
341     * @hide
342     */
343    public void clearMutated() {
344        super.clearMutated();
345        mState.mDrawable.clearMutated();
346        mMutated = false;
347    }
348
349    final static class ClipState extends ConstantState {
350        int[] mThemeAttrs;
351        int mChangingConfigurations;
352
353        Drawable mDrawable;
354
355        int mOrientation = HORIZONTAL;
356        int mGravity = Gravity.LEFT;
357
358        private boolean mCheckedConstantState;
359        private boolean mCanConstantState;
360
361        ClipState(ClipState orig, ClipDrawable owner, Resources res) {
362            if (orig != null) {
363                mThemeAttrs = orig.mThemeAttrs;
364                mChangingConfigurations = orig.mChangingConfigurations;
365                if (res != null) {
366                    mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
367                } else {
368                    mDrawable = orig.mDrawable.getConstantState().newDrawable();
369                }
370                mDrawable.setCallback(owner);
371                mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection());
372                mDrawable.setBounds(orig.mDrawable.getBounds());
373                mDrawable.setLevel(orig.mDrawable.getLevel());
374                mOrientation = orig.mOrientation;
375                mGravity = orig.mGravity;
376                mCheckedConstantState = mCanConstantState = true;
377            }
378        }
379
380        @Override
381        public boolean canApplyTheme() {
382            return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme())
383                    || super.canApplyTheme();
384        }
385
386        @Override
387        public Drawable newDrawable() {
388            return new ClipDrawable(this, null);
389        }
390
391        @Override
392        public Drawable newDrawable(Resources res) {
393            return new ClipDrawable(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 ClipDrawable(ClipState state, Resources res) {
412        mState = new ClipState(state, this, res);
413    }
414}
415
416