ClipDrawable.java revision f2a47782f31b58d2d31bd00b50fe43604af8b9c2
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 org.xmlpull.v1.XmlPullParser;
20import org.xmlpull.v1.XmlPullParserException;
21
22import android.content.res.Resources;
23import android.content.res.TypedArray;
24import android.graphics.*;
25import android.view.Gravity;
26import android.util.AttributeSet;
27
28import java.io.IOException;
29
30/**
31 * A Drawable that clips another Drawable based on this Drawable's current
32 * level value.  You can control how much the child Drawable gets clipped in width
33 * and height based on the level, as well as a gravity to control where it is
34 * placed in its overall container.  Most often used to implement things like
35 * progress bars, by increasing the drawable's level with {@link
36 * android.graphics.drawable.Drawable#setLevel(int) setLevel()}.
37 * <p class="note"><strong>Note:</strong> The drawable is clipped completely and not visible when
38 * the level is 0 and fully revealed when the level is 10,000.</p>
39 *
40 * <p>It can be defined in an XML file with the <code>&lt;clip></code> element.  For more
41 * information, see the guide to <a
42 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
43 *
44 * @attr ref android.R.styleable#ClipDrawable_clipOrientation
45 * @attr ref android.R.styleable#ClipDrawable_gravity
46 * @attr ref android.R.styleable#ClipDrawable_drawable
47 */
48public class ClipDrawable extends Drawable implements Drawable.Callback {
49    private ClipState mClipState;
50    private final Rect mTmpRect = new Rect();
51
52    public static final int HORIZONTAL = 1;
53    public static final int VERTICAL = 2;
54
55    ClipDrawable() {
56        this(null, null);
57    }
58
59    /**
60     * @param orientation Bitwise-or of {@link #HORIZONTAL} and/or {@link #VERTICAL}
61     */
62    public ClipDrawable(Drawable drawable, int gravity, int orientation) {
63        this(null, null);
64
65        mClipState.mDrawable = drawable;
66        mClipState.mGravity = gravity;
67        mClipState.mOrientation = orientation;
68
69        if (drawable != null) {
70            drawable.setCallback(this);
71        }
72    }
73
74    @Override
75    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
76            throws XmlPullParserException, IOException {
77        super.inflate(r, parser, attrs);
78
79        int type;
80
81        TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.ClipDrawable);
82
83        int orientation = a.getInt(
84                com.android.internal.R.styleable.ClipDrawable_clipOrientation,
85                HORIZONTAL);
86        int g = a.getInt(com.android.internal.R.styleable.ClipDrawable_gravity, Gravity.LEFT);
87        Drawable dr = a.getDrawable(com.android.internal.R.styleable.ClipDrawable_drawable);
88
89        a.recycle();
90
91        final int outerDepth = parser.getDepth();
92        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
93                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
94            if (type != XmlPullParser.START_TAG) {
95                continue;
96            }
97            dr = Drawable.createFromXmlInner(r, parser, attrs);
98        }
99
100        if (dr == null) {
101            throw new IllegalArgumentException("No drawable specified for <clip>");
102        }
103
104        mClipState.mDrawable = dr;
105        mClipState.mOrientation = orientation;
106        mClipState.mGravity = g;
107
108        dr.setCallback(this);
109    }
110
111    // overrides from Drawable.Callback
112
113    public void invalidateDrawable(Drawable who) {
114        final Callback callback = getCallback();
115        if (callback != null) {
116            callback.invalidateDrawable(this);
117        }
118    }
119
120    public void scheduleDrawable(Drawable who, Runnable what, long when) {
121        final Callback callback = getCallback();
122        if (callback != null) {
123            callback.scheduleDrawable(this, what, when);
124        }
125    }
126
127    public void unscheduleDrawable(Drawable who, Runnable what) {
128        final Callback callback = getCallback();
129        if (callback != null) {
130            callback.unscheduleDrawable(this, what);
131        }
132    }
133
134    // overrides from Drawable
135
136    @Override
137    public int getChangingConfigurations() {
138        return super.getChangingConfigurations()
139                | mClipState.mChangingConfigurations
140                | mClipState.mDrawable.getChangingConfigurations();
141    }
142
143    @Override
144    public boolean getPadding(Rect padding) {
145        // XXX need to adjust padding!
146        return mClipState.mDrawable.getPadding(padding);
147    }
148
149    @Override
150    public boolean setVisible(boolean visible, boolean restart) {
151        mClipState.mDrawable.setVisible(visible, restart);
152        return super.setVisible(visible, restart);
153    }
154
155    @Override
156    public void setAlpha(int alpha) {
157        mClipState.mDrawable.setAlpha(alpha);
158    }
159
160    @Override
161    public void setColorFilter(ColorFilter cf) {
162        mClipState.mDrawable.setColorFilter(cf);
163    }
164
165    @Override
166    public int getOpacity() {
167        return mClipState.mDrawable.getOpacity();
168    }
169
170    @Override
171    public boolean isStateful() {
172        return mClipState.mDrawable.isStateful();
173    }
174
175    @Override
176    protected boolean onStateChange(int[] state) {
177        return mClipState.mDrawable.setState(state);
178    }
179
180    @Override
181    protected boolean onLevelChange(int level) {
182        mClipState.mDrawable.setLevel(level);
183        invalidateSelf();
184        return true;
185    }
186
187    @Override
188    protected void onBoundsChange(Rect bounds) {
189        mClipState.mDrawable.setBounds(bounds);
190    }
191
192    @Override
193    public void draw(Canvas canvas) {
194
195        if (mClipState.mDrawable.getLevel() == 0) {
196            return;
197        }
198
199        final Rect r = mTmpRect;
200        final Rect bounds = getBounds();
201        int level = getLevel();
202        int w = bounds.width();
203        final int iw = 0; //mClipState.mDrawable.getIntrinsicWidth();
204        if ((mClipState.mOrientation & HORIZONTAL) != 0) {
205            w -= (w - iw) * (10000 - level) / 10000;
206        }
207        int h = bounds.height();
208        final int ih = 0; //mClipState.mDrawable.getIntrinsicHeight();
209        if ((mClipState.mOrientation & VERTICAL) != 0) {
210            h -= (h - ih) * (10000 - level) / 10000;
211        }
212        Gravity.apply(mClipState.mGravity, w, h, bounds, r);
213
214        if (w > 0 && h > 0) {
215            canvas.save();
216            canvas.clipRect(r);
217            mClipState.mDrawable.draw(canvas);
218            canvas.restore();
219        }
220    }
221
222    @Override
223    public int getIntrinsicWidth() {
224        return mClipState.mDrawable.getIntrinsicWidth();
225    }
226
227    @Override
228    public int getIntrinsicHeight() {
229        return mClipState.mDrawable.getIntrinsicHeight();
230    }
231
232    @Override
233    public ConstantState getConstantState() {
234        if (mClipState.canConstantState()) {
235            mClipState.mChangingConfigurations = super.getChangingConfigurations();
236            return mClipState;
237        }
238        return null;
239    }
240
241
242
243    final static class ClipState extends ConstantState {
244        Drawable mDrawable;
245        int mChangingConfigurations;
246        int mOrientation;
247        int mGravity;
248
249        private boolean mCheckedConstantState;
250        private boolean mCanConstantState;
251
252        ClipState(ClipState orig, ClipDrawable owner, Resources res) {
253            if (orig != null) {
254                if (res != null) {
255                    mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
256                } else {
257                    mDrawable = orig.mDrawable.getConstantState().newDrawable();
258                }
259                mDrawable.setCallback(owner);
260                mOrientation = orig.mOrientation;
261                mGravity = orig.mGravity;
262                mCheckedConstantState = mCanConstantState = true;
263            }
264        }
265
266        @Override
267        public Drawable newDrawable() {
268            return new ClipDrawable(this, null);
269        }
270
271        @Override
272        public Drawable newDrawable(Resources res) {
273            return new ClipDrawable(this, res);
274        }
275
276        @Override
277        public int getChangingConfigurations() {
278            return mChangingConfigurations;
279        }
280
281        boolean canConstantState() {
282            if (!mCheckedConstantState) {
283                mCanConstantState = mDrawable.getConstantState() != null;
284                mCheckedConstantState = true;
285            }
286
287            return mCanConstantState;
288        }
289    }
290
291    private ClipDrawable(ClipState state, Resources res) {
292        mClipState = new ClipState(state, this, res);
293    }
294}
295
296