ScaleDrawable.java revision d21fd9d1ccd2b525f9c004a6cd9ba19a645701ab
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 changes the size of another Drawable based on its current
37 * level value.  You can control how much the child Drawable changes 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.
41 *
42 * <p>It can be defined in an XML file with the <code>&lt;scale></code> element. For more
43 * information, see the guide to <a
44 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
45 *
46 * @attr ref android.R.styleable#ScaleDrawable_scaleWidth
47 * @attr ref android.R.styleable#ScaleDrawable_scaleHeight
48 * @attr ref android.R.styleable#ScaleDrawable_scaleGravity
49 * @attr ref android.R.styleable#ScaleDrawable_drawable
50 */
51public class ScaleDrawable extends Drawable implements Drawable.Callback {
52    private ScaleState mState;
53    private boolean mMutated;
54    private final Rect mTmpRect = new Rect();
55
56    ScaleDrawable() {
57        this(null, null);
58    }
59
60    public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) {
61        this(null, null);
62
63        mState.mDrawable = drawable;
64        mState.mGravity = gravity;
65        mState.mScaleWidth = scaleWidth;
66        mState.mScaleHeight = scaleHeight;
67
68        if (drawable != null) {
69            drawable.setCallback(this);
70        }
71    }
72
73    /**
74     * Returns the drawable scaled by this ScaleDrawable.
75     */
76    public Drawable getDrawable() {
77        return mState.mDrawable;
78    }
79
80    private static float getPercent(TypedArray a, int name, float defaultValue) {
81        final String s = a.getString(name);
82        if (s != null) {
83            if (s.endsWith("%")) {
84                String f = s.substring(0, s.length() - 1);
85                return Float.parseFloat(f) / 100.0f;
86            }
87        }
88        return defaultValue;
89    }
90
91    @Override
92    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
93            throws XmlPullParserException, IOException {
94        super.inflate(r, parser, attrs, theme);
95
96        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable);
97
98        // Reset mDrawable to preserve old multiple-inflate behavior. This is
99        // silly, but we have CTS tests that rely on it.
100        mState.mDrawable = null;
101
102        updateStateFromTypedArray(a);
103        inflateChildElements(r, parser, attrs, theme);
104        verifyRequiredAttributes(a);
105        a.recycle();
106    }
107
108    @Override
109    public void applyTheme(Theme t) {
110        super.applyTheme(t);
111
112        final ScaleState state = mState;
113        if (state == null) {
114            return;
115        }
116
117        if (state.mThemeAttrs == null) {
118            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ScaleDrawable);
119            try {
120                updateStateFromTypedArray(a);
121                verifyRequiredAttributes(a);
122            } catch (XmlPullParserException e) {
123                throw new RuntimeException(e);
124            } finally {
125                a.recycle();
126            }
127        }
128
129        if (state.mDrawable != null && state.mDrawable.canApplyTheme()) {
130            state.mDrawable.applyTheme(t);
131        }
132    }
133
134    private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
135            Theme theme) throws XmlPullParserException, IOException {
136        Drawable dr = null;
137        int type;
138        final int outerDepth = parser.getDepth();
139        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
140                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
141            if (type != XmlPullParser.START_TAG) {
142                continue;
143            }
144            dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
145        }
146
147        if (dr != null) {
148            mState.mDrawable = dr;
149            dr.setCallback(this);
150        }
151    }
152
153    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
154        // If we're not waiting on a theme, verify required attributes.
155        if (mState.mDrawable == null && (mState.mThemeAttrs == null
156                || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) {
157            throw new XmlPullParserException(a.getPositionDescription()
158                    + ": <scale> tag requires a 'drawable' attribute or "
159                    + "child tag defining a drawable");
160        }
161    }
162
163    private void updateStateFromTypedArray(TypedArray a) {
164        final ScaleState state = mState;
165
166        // Account for any configuration changes.
167        state.mChangingConfigurations |= a.getChangingConfigurations();
168
169        // Extract the theme attributes, if any.
170        state.mThemeAttrs = a.extractThemeAttrs();
171
172        state.mScaleWidth = getPercent(
173                a, R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth);
174        state.mScaleHeight = getPercent(
175                a, R.styleable.ScaleDrawable_scaleHeight, state.mScaleHeight);
176        state.mGravity = a.getInt(R.styleable.ScaleDrawable_scaleGravity, Gravity.LEFT);
177        state.mUseIntrinsicSizeAsMin = a.getBoolean(
178                R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, false);
179
180        final Drawable dr = a.getDrawable(R.styleable.ScaleDrawable_drawable);
181        if (dr != null) {
182            state.mDrawable = dr;
183            dr.setCallback(this);
184        }
185    }
186
187    @Override
188    public boolean canApplyTheme() {
189        return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
190    }
191
192    // overrides from Drawable.Callback
193
194    public void invalidateDrawable(Drawable who) {
195        if (getCallback() != null) {
196            getCallback().invalidateDrawable(this);
197        }
198    }
199
200    public void scheduleDrawable(Drawable who, Runnable what, long when) {
201        if (getCallback() != null) {
202            getCallback().scheduleDrawable(this, what, when);
203        }
204    }
205
206    public void unscheduleDrawable(Drawable who, Runnable what) {
207        if (getCallback() != null) {
208            getCallback().unscheduleDrawable(this, what);
209        }
210    }
211
212    // overrides from Drawable
213
214    @Override
215    public void draw(Canvas canvas) {
216        if (mState.mDrawable.getLevel() != 0)
217            mState.mDrawable.draw(canvas);
218    }
219
220    @Override
221    public int getChangingConfigurations() {
222        return super.getChangingConfigurations()
223                | mState.mChangingConfigurations
224                | mState.mDrawable.getChangingConfigurations();
225    }
226
227    @Override
228    public boolean getPadding(Rect padding) {
229        // XXX need to adjust padding!
230        return mState.mDrawable.getPadding(padding);
231    }
232
233    @Override
234    public boolean setVisible(boolean visible, boolean restart) {
235        mState.mDrawable.setVisible(visible, restart);
236        return super.setVisible(visible, restart);
237    }
238
239    @Override
240    public void setAlpha(int alpha) {
241        mState.mDrawable.setAlpha(alpha);
242    }
243
244    @Override
245    public int getAlpha() {
246        return mState.mDrawable.getAlpha();
247    }
248
249    @Override
250    public void setColorFilter(ColorFilter cf) {
251        mState.mDrawable.setColorFilter(cf);
252    }
253
254    @Override
255    public void setTintList(ColorStateList tint) {
256        mState.mDrawable.setTintList(tint);
257    }
258
259    @Override
260    public void setTintMode(Mode tintMode) {
261        mState.mDrawable.setTintMode(tintMode);
262    }
263
264    @Override
265    public int getOpacity() {
266        return mState.mDrawable.getOpacity();
267    }
268
269    @Override
270    public boolean isStateful() {
271        return mState.mDrawable.isStateful();
272    }
273
274    @Override
275    protected boolean onStateChange(int[] state) {
276        boolean changed = mState.mDrawable.setState(state);
277        onBoundsChange(getBounds());
278        return changed;
279    }
280
281    @Override
282    protected boolean onLevelChange(int level) {
283        mState.mDrawable.setLevel(level);
284        onBoundsChange(getBounds());
285        invalidateSelf();
286        return true;
287    }
288
289    @Override
290    protected void onBoundsChange(Rect bounds) {
291        final Rect r = mTmpRect;
292        final boolean min = mState.mUseIntrinsicSizeAsMin;
293        int level = getLevel();
294        int w = bounds.width();
295        if (mState.mScaleWidth > 0) {
296            final int iw = min ? mState.mDrawable.getIntrinsicWidth() : 0;
297            w -= (int) ((w - iw) * (10000 - level) * mState.mScaleWidth / 10000);
298        }
299        int h = bounds.height();
300        if (mState.mScaleHeight > 0) {
301            final int ih = min ? mState.mDrawable.getIntrinsicHeight() : 0;
302            h -= (int) ((h - ih) * (10000 - level) * mState.mScaleHeight / 10000);
303        }
304        final int layoutDirection = getLayoutDirection();
305        Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection);
306
307        if (w > 0 && h > 0) {
308            mState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom);
309        }
310    }
311
312    @Override
313    public int getIntrinsicWidth() {
314        return mState.mDrawable.getIntrinsicWidth();
315    }
316
317    @Override
318    public int getIntrinsicHeight() {
319        return mState.mDrawable.getIntrinsicHeight();
320    }
321
322    @Override
323    public ConstantState getConstantState() {
324        if (mState.canConstantState()) {
325            mState.mChangingConfigurations = getChangingConfigurations();
326            return mState;
327        }
328        return null;
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 ScaleState extends ConstantState {
350        int[] mThemeAttrs;
351        int mChangingConfigurations;
352
353        Drawable mDrawable;
354
355        float mScaleWidth = 1.0f;
356        float mScaleHeight = 1.0f;
357        int mGravity = Gravity.LEFT;
358        boolean mUseIntrinsicSizeAsMin = false;
359
360        private boolean mCheckedConstantState;
361        private boolean mCanConstantState;
362
363        ScaleState(ScaleState orig, ScaleDrawable owner, Resources res) {
364            if (orig != null) {
365                mThemeAttrs = orig.mThemeAttrs;
366                mChangingConfigurations = orig.mChangingConfigurations;
367                if (res != null) {
368                    mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
369                } else {
370                    mDrawable = orig.mDrawable.getConstantState().newDrawable();
371                }
372                mDrawable.setCallback(owner);
373                mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection());
374                mDrawable.setBounds(orig.mDrawable.getBounds());
375                mDrawable.setLevel(orig.mDrawable.getLevel());
376                mScaleWidth = orig.mScaleWidth;
377                mScaleHeight = orig.mScaleHeight;
378                mGravity = orig.mGravity;
379                mUseIntrinsicSizeAsMin = orig.mUseIntrinsicSizeAsMin;
380                mCheckedConstantState = mCanConstantState = true;
381            }
382        }
383
384        @Override
385        public boolean canApplyTheme() {
386            return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme())
387                    || super.canApplyTheme();
388        }
389
390        @Override
391        public Drawable newDrawable() {
392            return new ScaleDrawable(this, null);
393        }
394
395        @Override
396        public Drawable newDrawable(Resources res) {
397            return new ScaleDrawable(this, res);
398        }
399
400        @Override
401        public int getChangingConfigurations() {
402            return mChangingConfigurations;
403        }
404
405        boolean canConstantState() {
406            if (!mCheckedConstantState) {
407                mCanConstantState = mDrawable.getConstantState() != null;
408                mCheckedConstantState = true;
409            }
410
411            return mCanConstantState;
412        }
413    }
414
415    private ScaleDrawable(ScaleState state, Resources res) {
416        mState = new ScaleState(state, this, res);
417    }
418}
419
420