ClipDrawable.java revision 3da32b768899e7dabe3a16333edf5eca2b9ebe93
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.Resources;
25import android.content.res.TypedArray;
26import android.content.res.Resources.Theme;
27import android.graphics.*;
28import android.view.Gravity;
29import android.util.AttributeSet;
30
31import java.io.IOException;
32
33/**
34 * A Drawable that clips another Drawable based on this Drawable's current
35 * level value.  You can control how much the child Drawable gets clipped in width
36 * and height based on the level, as well as a gravity to control where it is
37 * placed in its overall container.  Most often used to implement things like
38 * progress bars, by increasing the drawable's level with {@link
39 * android.graphics.drawable.Drawable#setLevel(int) setLevel()}.
40 * <p class="note"><strong>Note:</strong> The drawable is clipped completely and not visible when
41 * the level is 0 and fully revealed when the level is 10,000.</p>
42 *
43 * <p>It can be defined in an XML file with the <code>&lt;clip></code> element.  For more
44 * information, see the guide to <a
45 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
46 *
47 * @attr ref android.R.styleable#ClipDrawable_clipOrientation
48 * @attr ref android.R.styleable#ClipDrawable_gravity
49 * @attr ref android.R.styleable#ClipDrawable_drawable
50 */
51public class ClipDrawable extends DrawableWrapper {
52    public static final int HORIZONTAL = 1;
53    public static final int VERTICAL = 2;
54
55    private final Rect mTmpRect = new Rect();
56
57    private ClipState mState;
58
59    ClipDrawable() {
60        this(new ClipState(null), null);
61    }
62
63    /**
64     * Creates a new clip drawable with the specified gravity and orientation.
65     *
66     * @param drawable the drawable to clip
67     * @param gravity gravity constant (see {@link Gravity} used to position
68     *                the clipped drawable within the parent container
69     * @param orientation bitwise-or of {@link #HORIZONTAL} and/or
70     *                   {@link #VERTICAL}
71     */
72    public ClipDrawable(Drawable drawable, int gravity, int orientation) {
73        this(new ClipState(null), null);
74
75        mState.mGravity = gravity;
76        mState.mOrientation = orientation;
77
78        setDrawable(drawable);
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        updateStateFromTypedArray(a);
88        inflateChildDrawable(r, parser, attrs, theme);
89        verifyRequiredAttributes(a);
90        a.recycle();
91    }
92
93    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
94        // If we're not waiting on a theme, verify required attributes.
95        if (getDrawable() == null && (mState.mThemeAttrs == null
96                || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) {
97            throw new XmlPullParserException(a.getPositionDescription()
98                    + ": <clip> tag requires a 'drawable' attribute or "
99                    + "child tag defining a drawable");
100        }
101    }
102
103    @Override
104    void updateStateFromTypedArray(TypedArray a) {
105        super.updateStateFromTypedArray(a);
106
107        final ClipState state = mState;
108        state.mOrientation = a.getInt(
109                R.styleable.ClipDrawable_clipOrientation, state.mOrientation);
110        state.mGravity = a.getInt(
111                R.styleable.ClipDrawable_gravity, state.mGravity);
112
113        final Drawable dr = a.getDrawable(R.styleable.ClipDrawable_drawable);
114        if (dr != null) {
115            setDrawable(dr);
116        }
117    }
118
119    @Override
120    public void applyTheme(Theme t) {
121        final ClipState state = mState;
122        if (state == null) {
123            return;
124        }
125
126        if (state.mThemeAttrs != null) {
127            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ClipDrawable);
128            try {
129                updateStateFromTypedArray(a);
130                verifyRequiredAttributes(a);
131            } catch (XmlPullParserException e) {
132                throw new RuntimeException(e);
133            } finally {
134                a.recycle();
135            }
136        }
137
138        // The drawable may have changed as a result of applying the theme, so
139        // apply the theme to the wrapped drawable last.
140        super.applyTheme(t);
141    }
142
143    @Override
144    protected boolean onLevelChange(float level) {
145        super.onLevelChange(level);
146        invalidateSelf();
147        return true;
148    }
149
150    @Override
151    public int getOpacity() {
152        final Drawable dr = getDrawable();
153        final int opacity = dr.getOpacity();
154        if (opacity == PixelFormat.TRANSPARENT || dr.getLevelFloat() == 0) {
155            return PixelFormat.TRANSPARENT;
156        }
157
158        final float level = getLevelFloat();
159        if (level >= MAX_LEVEL_FLOAT) {
160            return dr.getOpacity();
161        }
162
163        // Some portion of non-transparent drawable is showing.
164        return PixelFormat.TRANSLUCENT;
165    }
166
167    @Override
168    public void draw(Canvas canvas) {
169        final Drawable dr = getDrawable();
170        if (dr.getLevelFloat() == 0) {
171            return;
172        }
173
174        final Rect r = mTmpRect;
175        final Rect bounds = getBounds();
176        final float level = getLevelFloat();
177
178        int w = bounds.width();
179        final int iw = 0;
180        if ((mState.mOrientation & HORIZONTAL) != 0) {
181            w -= Math.round((w - iw) * (MAX_LEVEL_FLOAT - level) / MAX_LEVEL_FLOAT);
182        }
183
184        int h = bounds.height();
185        final int ih = 0;
186        if ((mState.mOrientation & VERTICAL) != 0) {
187            h -= Math.round((h - ih) * (MAX_LEVEL_FLOAT - level) / MAX_LEVEL_FLOAT);
188        }
189
190        final int layoutDirection = getLayoutDirection();
191        Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection);
192
193        if (w > 0 && h > 0) {
194            canvas.save();
195            canvas.clipRect(r);
196            dr.draw(canvas);
197            canvas.restore();
198        }
199    }
200
201    static final class ClipState extends DrawableWrapper.DrawableWrapperState {
202        int mOrientation = HORIZONTAL;
203        int mGravity = Gravity.LEFT;
204
205        ClipState(ClipState orig) {
206            super(orig);
207
208            if (orig != null) {
209                mOrientation = orig.mOrientation;
210                mGravity = orig.mGravity;
211            }
212        }
213
214        @Override
215        public Drawable newDrawable(Resources res) {
216            return new ClipDrawable(this, res);
217        }
218    }
219
220    private ClipDrawable(ClipState state, Resources res) {
221        super(state, res);
222
223        mState = state;
224    }
225}
226
227