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.annotation.NonNull;
25import android.annotation.Nullable;
26import android.content.res.Resources;
27import android.content.res.TypedArray;
28import android.content.res.Resources.Theme;
29import android.graphics.*;
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 DrawableWrapper {
54    public static final int HORIZONTAL = 1;
55    public static final int VERTICAL = 2;
56
57    private static final int MAX_LEVEL = 10000;
58
59    private final Rect mTmpRect = new Rect();
60
61    private ClipState mState;
62
63    ClipDrawable() {
64        this(new ClipState(null, null), null);
65    }
66
67    /**
68     * Creates a new clip drawable with the specified gravity and orientation.
69     *
70     * @param drawable the drawable to clip
71     * @param gravity gravity constant (see {@link Gravity} used to position
72     *                the clipped drawable within the parent container
73     * @param orientation bitwise-or of {@link #HORIZONTAL} and/or
74     *                   {@link #VERTICAL}
75     */
76    public ClipDrawable(Drawable drawable, int gravity, int orientation) {
77        this(new ClipState(null, null), null);
78
79        mState.mGravity = gravity;
80        mState.mOrientation = orientation;
81
82        setDrawable(drawable);
83    }
84
85    @Override
86    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
87            @NonNull AttributeSet attrs, @Nullable Theme theme)
88            throws XmlPullParserException, IOException {
89        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable);
90
91        // Inflation will advance the XmlPullParser and AttributeSet.
92        super.inflate(r, parser, attrs, theme);
93
94        updateStateFromTypedArray(a);
95        verifyRequiredAttributes(a);
96        a.recycle();
97    }
98
99    @Override
100    public void applyTheme(@NonNull Theme t) {
101        super.applyTheme(t);
102
103        final ClipState state = mState;
104        if (state == null) {
105            return;
106        }
107
108        if (state.mThemeAttrs != null) {
109            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ClipDrawable);
110            try {
111                updateStateFromTypedArray(a);
112                verifyRequiredAttributes(a);
113            } catch (XmlPullParserException e) {
114                rethrowAsRuntimeException(e);
115            } finally {
116                a.recycle();
117            }
118        }
119    }
120
121    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
122        // If we're not waiting on a theme, verify required attributes.
123        if (getDrawable() == null && (mState.mThemeAttrs == null
124                || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) {
125            throw new XmlPullParserException(a.getPositionDescription()
126                    + ": <clip> tag requires a 'drawable' attribute or "
127                    + "child tag defining a drawable");
128        }
129    }
130
131    private void updateStateFromTypedArray(@NonNull TypedArray a) {
132        final ClipState state = mState;
133        if (state == null) {
134            return;
135        }
136
137        // Account for any configuration changes.
138        state.mChangingConfigurations |= a.getChangingConfigurations();
139
140        // Extract the theme attributes, if any.
141        state.mThemeAttrs = a.extractThemeAttrs();
142
143        state.mOrientation = a.getInt(
144                R.styleable.ClipDrawable_clipOrientation, state.mOrientation);
145        state.mGravity = a.getInt(
146                R.styleable.ClipDrawable_gravity, state.mGravity);
147    }
148
149    @Override
150    protected boolean onLevelChange(int level) {
151        super.onLevelChange(level);
152        invalidateSelf();
153        return true;
154    }
155
156    @Override
157    public int getOpacity() {
158        final Drawable dr = getDrawable();
159        final int opacity = dr.getOpacity();
160        if (opacity == PixelFormat.TRANSPARENT || dr.getLevel() == 0) {
161            return PixelFormat.TRANSPARENT;
162        }
163
164        final int level = getLevel();
165        if (level >= MAX_LEVEL) {
166            return dr.getOpacity();
167        }
168
169        // Some portion of non-transparent drawable is showing.
170        return PixelFormat.TRANSLUCENT;
171    }
172
173    @Override
174    public void draw(Canvas canvas) {
175        final Drawable dr = getDrawable();
176        if (dr.getLevel() == 0) {
177            return;
178        }
179
180        final Rect r = mTmpRect;
181        final Rect bounds = getBounds();
182        final int level = getLevel();
183
184        int w = bounds.width();
185        final int iw = 0; //mState.mDrawable.getIntrinsicWidth();
186        if ((mState.mOrientation & HORIZONTAL) != 0) {
187            w -= (w - iw) * (MAX_LEVEL - level) / MAX_LEVEL;
188        }
189
190        int h = bounds.height();
191        final int ih = 0; //mState.mDrawable.getIntrinsicHeight();
192        if ((mState.mOrientation & VERTICAL) != 0) {
193            h -= (h - ih) * (MAX_LEVEL - level) / MAX_LEVEL;
194        }
195
196        final int layoutDirection = getLayoutDirection();
197        Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection);
198
199        if (w > 0 && h > 0) {
200            canvas.save();
201            canvas.clipRect(r);
202            dr.draw(canvas);
203            canvas.restore();
204        }
205    }
206
207    @Override
208    DrawableWrapperState mutateConstantState() {
209        mState = new ClipState(mState, null);
210        return mState;
211    }
212
213    static final class ClipState extends DrawableWrapper.DrawableWrapperState {
214        private int[] mThemeAttrs;
215
216        int mOrientation = HORIZONTAL;
217        int mGravity = Gravity.LEFT;
218
219        ClipState(ClipState orig, Resources res) {
220            super(orig, res);
221
222            if (orig != null) {
223                mOrientation = orig.mOrientation;
224                mGravity = orig.mGravity;
225            }
226        }
227
228        @Override
229        public Drawable newDrawable(Resources res) {
230            return new ClipDrawable(this, res);
231        }
232    }
233
234    private ClipDrawable(ClipState state, Resources res) {
235        super(state, res);
236
237        mState = state;
238    }
239}
240
241