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        final int layoutDirection = getLayoutDirection();
213        Gravity.apply(mClipState.mGravity, w, h, bounds, r, layoutDirection);
214
215        if (w > 0 && h > 0) {
216            canvas.save();
217            canvas.clipRect(r);
218            mClipState.mDrawable.draw(canvas);
219            canvas.restore();
220        }
221    }
222
223    @Override
224    public int getIntrinsicWidth() {
225        return mClipState.mDrawable.getIntrinsicWidth();
226    }
227
228    @Override
229    public int getIntrinsicHeight() {
230        return mClipState.mDrawable.getIntrinsicHeight();
231    }
232
233    @Override
234    public ConstantState getConstantState() {
235        if (mClipState.canConstantState()) {
236            mClipState.mChangingConfigurations = getChangingConfigurations();
237            return mClipState;
238        }
239        return null;
240    }
241
242    /** @hide */
243    @Override
244    public void setLayoutDirection(int layoutDirection) {
245        mClipState.mDrawable.setLayoutDirection(layoutDirection);
246        super.setLayoutDirection(layoutDirection);
247    }
248
249    final static class ClipState extends ConstantState {
250        Drawable mDrawable;
251        int mChangingConfigurations;
252        int mOrientation;
253        int mGravity;
254
255        private boolean mCheckedConstantState;
256        private boolean mCanConstantState;
257
258        ClipState(ClipState orig, ClipDrawable owner, Resources res) {
259            if (orig != null) {
260                if (res != null) {
261                    mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
262                } else {
263                    mDrawable = orig.mDrawable.getConstantState().newDrawable();
264                }
265                mDrawable.setCallback(owner);
266                mOrientation = orig.mOrientation;
267                mGravity = orig.mGravity;
268                mCheckedConstantState = mCanConstantState = true;
269            }
270        }
271
272        @Override
273        public Drawable newDrawable() {
274            return new ClipDrawable(this, null);
275        }
276
277        @Override
278        public Drawable newDrawable(Resources res) {
279            return new ClipDrawable(this, res);
280        }
281
282        @Override
283        public int getChangingConfigurations() {
284            return mChangingConfigurations;
285        }
286
287        boolean canConstantState() {
288            if (!mCheckedConstantState) {
289                mCanConstantState = mDrawable.getConstantState() != null;
290                mCheckedConstantState = true;
291            }
292
293            return mCanConstantState;
294        }
295    }
296
297    private ClipDrawable(ClipState state, Resources res) {
298        mClipState = new ClipState(state, this, res);
299    }
300}
301
302