ScaleDrawable.java revision 17cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8b
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.ColorStateList;
23import android.content.res.Resources;
24import android.content.res.TypedArray;
25import android.content.res.Resources.Theme;
26import android.graphics.*;
27import android.graphics.PorterDuff.Mode;
28import android.view.Gravity;
29import android.util.AttributeSet;
30
31import java.io.IOException;
32
33/**
34 * A Drawable that changes the size of another Drawable based on its current
35 * level value.  You can control how much the child Drawable changes 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.
39 *
40 * <p>It can be defined in an XML file with the <code>&lt;scale></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#ScaleDrawable_scaleWidth
45 * @attr ref android.R.styleable#ScaleDrawable_scaleHeight
46 * @attr ref android.R.styleable#ScaleDrawable_scaleGravity
47 * @attr ref android.R.styleable#ScaleDrawable_drawable
48 */
49public class ScaleDrawable extends Drawable implements Drawable.Callback {
50    private ScaleState mScaleState;
51    private boolean mMutated;
52    private final Rect mTmpRect = new Rect();
53
54    ScaleDrawable() {
55        this(null, null);
56    }
57
58    public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) {
59        this(null, null);
60
61        mScaleState.mDrawable = drawable;
62        mScaleState.mGravity = gravity;
63        mScaleState.mScaleWidth = scaleWidth;
64        mScaleState.mScaleHeight = scaleHeight;
65
66        if (drawable != null) {
67            drawable.setCallback(this);
68        }
69    }
70
71    /**
72     * Returns the drawable scaled by this ScaleDrawable.
73     */
74    public Drawable getDrawable() {
75        return mScaleState.mDrawable;
76    }
77
78    private static float getPercent(TypedArray a, int name) {
79        String s = a.getString(name);
80        if (s != null) {
81            if (s.endsWith("%")) {
82                String f = s.substring(0, s.length() - 1);
83                return Float.parseFloat(f) / 100.0f;
84            }
85        }
86        return -1;
87    }
88
89    @Override
90    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
91            throws XmlPullParserException, IOException {
92        super.inflate(r, parser, attrs, theme);
93
94        int type;
95
96        TypedArray a = obtainAttributes(
97                r, theme, attrs, com.android.internal.R.styleable.ScaleDrawable);
98
99        float sw = getPercent(a, com.android.internal.R.styleable.ScaleDrawable_scaleWidth);
100        float sh = getPercent(a, com.android.internal.R.styleable.ScaleDrawable_scaleHeight);
101        int g = a.getInt(com.android.internal.R.styleable.ScaleDrawable_scaleGravity, Gravity.LEFT);
102        boolean min = a.getBoolean(
103                com.android.internal.R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, false);
104        Drawable dr = a.getDrawable(com.android.internal.R.styleable.ScaleDrawable_drawable);
105
106        a.recycle();
107
108        final int outerDepth = parser.getDepth();
109        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
110                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
111            if (type != XmlPullParser.START_TAG) {
112                continue;
113            }
114            dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
115        }
116
117        if (dr == null) {
118            throw new IllegalArgumentException("No drawable specified for <scale>");
119        }
120
121        mScaleState.mDrawable = dr;
122        mScaleState.mScaleWidth = sw;
123        mScaleState.mScaleHeight = sh;
124        mScaleState.mGravity = g;
125        mScaleState.mUseIntrinsicSizeAsMin = min;
126        if (dr != null) {
127            dr.setCallback(this);
128        }
129    }
130
131    @Override
132    public void applyTheme(Theme t) {
133        super.applyTheme(t);
134
135        final ScaleState state = mScaleState;
136        if (state == null) {
137            return;
138        }
139
140        if (state.mDrawable != null) {
141            state.mDrawable.applyTheme(t);
142        }
143    }
144
145    @Override
146    public boolean canApplyTheme() {
147        final ScaleState state = mScaleState;
148        return state != null && state.mDrawable != null && state.mDrawable.canApplyTheme();
149    }
150
151    // overrides from Drawable.Callback
152
153    public void invalidateDrawable(Drawable who) {
154        if (getCallback() != null) {
155            getCallback().invalidateDrawable(this);
156        }
157    }
158
159    public void scheduleDrawable(Drawable who, Runnable what, long when) {
160        if (getCallback() != null) {
161            getCallback().scheduleDrawable(this, what, when);
162        }
163    }
164
165    public void unscheduleDrawable(Drawable who, Runnable what) {
166        if (getCallback() != null) {
167            getCallback().unscheduleDrawable(this, what);
168        }
169    }
170
171    // overrides from Drawable
172
173    @Override
174    public void draw(Canvas canvas) {
175        if (mScaleState.mDrawable.getLevel() != 0)
176            mScaleState.mDrawable.draw(canvas);
177    }
178
179    @Override
180    public int getChangingConfigurations() {
181        return super.getChangingConfigurations()
182                | mScaleState.mChangingConfigurations
183                | mScaleState.mDrawable.getChangingConfigurations();
184    }
185
186    @Override
187    public boolean getPadding(Rect padding) {
188        // XXX need to adjust padding!
189        return mScaleState.mDrawable.getPadding(padding);
190    }
191
192    @Override
193    public boolean setVisible(boolean visible, boolean restart) {
194        mScaleState.mDrawable.setVisible(visible, restart);
195        return super.setVisible(visible, restart);
196    }
197
198    @Override
199    public void setAlpha(int alpha) {
200        mScaleState.mDrawable.setAlpha(alpha);
201    }
202
203    @Override
204    public int getAlpha() {
205        return mScaleState.mDrawable.getAlpha();
206    }
207
208    @Override
209    public void setColorFilter(ColorFilter cf) {
210        mScaleState.mDrawable.setColorFilter(cf);
211    }
212
213    @Override
214    public void setTintList(ColorStateList tint) {
215        mScaleState.mDrawable.setTintList(tint);
216    }
217
218    @Override
219    public void setTintMode(Mode tintMode) {
220        mScaleState.mDrawable.setTintMode(tintMode);
221    }
222
223    @Override
224    public int getOpacity() {
225        return mScaleState.mDrawable.getOpacity();
226    }
227
228    @Override
229    public boolean isStateful() {
230        return mScaleState.mDrawable.isStateful();
231    }
232
233    @Override
234    protected boolean onStateChange(int[] state) {
235        boolean changed = mScaleState.mDrawable.setState(state);
236        onBoundsChange(getBounds());
237        return changed;
238    }
239
240    @Override
241    protected boolean onLevelChange(int level) {
242        mScaleState.mDrawable.setLevel(level);
243        onBoundsChange(getBounds());
244        invalidateSelf();
245        return true;
246    }
247
248    @Override
249    protected void onBoundsChange(Rect bounds) {
250        final Rect r = mTmpRect;
251        final boolean min = mScaleState.mUseIntrinsicSizeAsMin;
252        int level = getLevel();
253        int w = bounds.width();
254        if (mScaleState.mScaleWidth > 0) {
255            final int iw = min ? mScaleState.mDrawable.getIntrinsicWidth() : 0;
256            w -= (int) ((w - iw) * (10000 - level) * mScaleState.mScaleWidth / 10000);
257        }
258        int h = bounds.height();
259        if (mScaleState.mScaleHeight > 0) {
260            final int ih = min ? mScaleState.mDrawable.getIntrinsicHeight() : 0;
261            h -= (int) ((h - ih) * (10000 - level) * mScaleState.mScaleHeight / 10000);
262        }
263        final int layoutDirection = getLayoutDirection();
264        Gravity.apply(mScaleState.mGravity, w, h, bounds, r, layoutDirection);
265
266        if (w > 0 && h > 0) {
267            mScaleState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom);
268        }
269    }
270
271    @Override
272    public int getIntrinsicWidth() {
273        return mScaleState.mDrawable.getIntrinsicWidth();
274    }
275
276    @Override
277    public int getIntrinsicHeight() {
278        return mScaleState.mDrawable.getIntrinsicHeight();
279    }
280
281    @Override
282    public ConstantState getConstantState() {
283        if (mScaleState.canConstantState()) {
284            mScaleState.mChangingConfigurations = getChangingConfigurations();
285            return mScaleState;
286        }
287        return null;
288    }
289
290    @Override
291    public Drawable mutate() {
292        if (!mMutated && super.mutate() == this) {
293            mScaleState.mDrawable.mutate();
294            mMutated = true;
295        }
296        return this;
297    }
298
299    /**
300     * @hide
301     */
302    public void clearMutated() {
303        super.clearMutated();
304        mScaleState.mDrawable.clearMutated();
305        mMutated = false;
306    }
307
308    final static class ScaleState extends ConstantState {
309        Drawable mDrawable;
310        int mChangingConfigurations;
311        float mScaleWidth;
312        float mScaleHeight;
313        int mGravity;
314        boolean mUseIntrinsicSizeAsMin;
315
316        private boolean mCheckedConstantState;
317        private boolean mCanConstantState;
318
319        ScaleState(ScaleState orig, ScaleDrawable owner, Resources res) {
320            if (orig != null) {
321                if (res != null) {
322                    mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
323                } else {
324                    mDrawable = orig.mDrawable.getConstantState().newDrawable();
325                }
326                mDrawable.setCallback(owner);
327                mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection());
328                mDrawable.setBounds(orig.mDrawable.getBounds());
329                mDrawable.setLevel(orig.mDrawable.getLevel());
330                mScaleWidth = orig.mScaleWidth;
331                mScaleHeight = orig.mScaleHeight;
332                mGravity = orig.mGravity;
333                mUseIntrinsicSizeAsMin = orig.mUseIntrinsicSizeAsMin;
334                mCheckedConstantState = mCanConstantState = true;
335            }
336        }
337
338        @Override
339        public Drawable newDrawable() {
340            return new ScaleDrawable(this, null);
341        }
342
343        @Override
344        public Drawable newDrawable(Resources res) {
345            return new ScaleDrawable(this, res);
346        }
347
348        @Override
349        public int getChangingConfigurations() {
350            return mChangingConfigurations;
351        }
352
353        boolean canConstantState() {
354            if (!mCheckedConstantState) {
355                mCanConstantState = mDrawable.getConstantState() != null;
356                mCheckedConstantState = true;
357            }
358
359            return mCanConstantState;
360        }
361    }
362
363    private ScaleDrawable(ScaleState state, Resources res) {
364        mScaleState = new ScaleState(state, this, res);
365    }
366}
367
368