ScaleDrawable.java revision 727cae197b123ef764a1f8fbe08a995b000d14c3
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    // overrides from Drawable.Callback
132
133    public void invalidateDrawable(Drawable who) {
134        if (getCallback() != null) {
135            getCallback().invalidateDrawable(this);
136        }
137    }
138
139    public void scheduleDrawable(Drawable who, Runnable what, long when) {
140        if (getCallback() != null) {
141            getCallback().scheduleDrawable(this, what, when);
142        }
143    }
144
145    public void unscheduleDrawable(Drawable who, Runnable what) {
146        if (getCallback() != null) {
147            getCallback().unscheduleDrawable(this, what);
148        }
149    }
150
151    // overrides from Drawable
152
153    @Override
154    public void draw(Canvas canvas) {
155        if (mScaleState.mDrawable.getLevel() != 0)
156            mScaleState.mDrawable.draw(canvas);
157    }
158
159    @Override
160    public int getChangingConfigurations() {
161        return super.getChangingConfigurations()
162                | mScaleState.mChangingConfigurations
163                | mScaleState.mDrawable.getChangingConfigurations();
164    }
165
166    @Override
167    public boolean getPadding(Rect padding) {
168        // XXX need to adjust padding!
169        return mScaleState.mDrawable.getPadding(padding);
170    }
171
172    @Override
173    public boolean setVisible(boolean visible, boolean restart) {
174        mScaleState.mDrawable.setVisible(visible, restart);
175        return super.setVisible(visible, restart);
176    }
177
178    @Override
179    public void setAlpha(int alpha) {
180        mScaleState.mDrawable.setAlpha(alpha);
181    }
182
183    @Override
184    public int getAlpha() {
185        return mScaleState.mDrawable.getAlpha();
186    }
187
188    @Override
189    public void setColorFilter(ColorFilter cf) {
190        mScaleState.mDrawable.setColorFilter(cf);
191    }
192
193    @Override
194    public void setTintList(ColorStateList tint) {
195        mScaleState.mDrawable.setTintList(tint);
196    }
197
198    @Override
199    public void setTintMode(Mode tintMode) {
200        mScaleState.mDrawable.setTintMode(tintMode);
201    }
202
203    @Override
204    public int getOpacity() {
205        return mScaleState.mDrawable.getOpacity();
206    }
207
208    @Override
209    public boolean isStateful() {
210        return mScaleState.mDrawable.isStateful();
211    }
212
213    @Override
214    protected boolean onStateChange(int[] state) {
215        boolean changed = mScaleState.mDrawable.setState(state);
216        onBoundsChange(getBounds());
217        return changed;
218    }
219
220    @Override
221    protected boolean onLevelChange(int level) {
222        mScaleState.mDrawable.setLevel(level);
223        onBoundsChange(getBounds());
224        invalidateSelf();
225        return true;
226    }
227
228    @Override
229    protected void onBoundsChange(Rect bounds) {
230        final Rect r = mTmpRect;
231        final boolean min = mScaleState.mUseIntrinsicSizeAsMin;
232        int level = getLevel();
233        int w = bounds.width();
234        if (mScaleState.mScaleWidth > 0) {
235            final int iw = min ? mScaleState.mDrawable.getIntrinsicWidth() : 0;
236            w -= (int) ((w - iw) * (10000 - level) * mScaleState.mScaleWidth / 10000);
237        }
238        int h = bounds.height();
239        if (mScaleState.mScaleHeight > 0) {
240            final int ih = min ? mScaleState.mDrawable.getIntrinsicHeight() : 0;
241            h -= (int) ((h - ih) * (10000 - level) * mScaleState.mScaleHeight / 10000);
242        }
243        final int layoutDirection = getLayoutDirection();
244        Gravity.apply(mScaleState.mGravity, w, h, bounds, r, layoutDirection);
245
246        if (w > 0 && h > 0) {
247            mScaleState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom);
248        }
249    }
250
251    @Override
252    public int getIntrinsicWidth() {
253        return mScaleState.mDrawable.getIntrinsicWidth();
254    }
255
256    @Override
257    public int getIntrinsicHeight() {
258        return mScaleState.mDrawable.getIntrinsicHeight();
259    }
260
261    @Override
262    public ConstantState getConstantState() {
263        if (mScaleState.canConstantState()) {
264            mScaleState.mChangingConfigurations = getChangingConfigurations();
265            return mScaleState;
266        }
267        return null;
268    }
269
270    @Override
271    public Drawable mutate() {
272        if (!mMutated && super.mutate() == this) {
273            mScaleState.mDrawable.mutate();
274            mMutated = true;
275        }
276        return this;
277    }
278
279    /**
280     * @hide
281     */
282    public void clearMutated() {
283        super.clearMutated();
284        mScaleState.mDrawable.clearMutated();
285        mMutated = false;
286    }
287
288    final static class ScaleState extends ConstantState {
289        Drawable mDrawable;
290        int mChangingConfigurations;
291        float mScaleWidth;
292        float mScaleHeight;
293        int mGravity;
294        boolean mUseIntrinsicSizeAsMin;
295
296        private boolean mCheckedConstantState;
297        private boolean mCanConstantState;
298
299        ScaleState(ScaleState orig, ScaleDrawable owner, Resources res) {
300            if (orig != null) {
301                if (res != null) {
302                    mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
303                } else {
304                    mDrawable = orig.mDrawable.getConstantState().newDrawable();
305                }
306                mDrawable.setCallback(owner);
307                mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection());
308                mDrawable.setBounds(orig.mDrawable.getBounds());
309                mDrawable.setLevel(orig.mDrawable.getLevel());
310                mScaleWidth = orig.mScaleWidth;
311                mScaleHeight = orig.mScaleHeight;
312                mGravity = orig.mGravity;
313                mUseIntrinsicSizeAsMin = orig.mUseIntrinsicSizeAsMin;
314                mCheckedConstantState = mCanConstantState = true;
315            }
316        }
317
318        @Override
319        public Drawable newDrawable() {
320            return new ScaleDrawable(this, null);
321        }
322
323        @Override
324        public Drawable newDrawable(Resources res) {
325            return new ScaleDrawable(this, res);
326        }
327
328        @Override
329        public int getChangingConfigurations() {
330            return mChangingConfigurations;
331        }
332
333        boolean canConstantState() {
334            if (!mCheckedConstantState) {
335                mCanConstantState = mDrawable.getConstantState() != null;
336                mCheckedConstantState = true;
337            }
338
339            return mCanConstantState;
340        }
341    }
342
343    private ScaleDrawable(ScaleState state, Resources res) {
344        mScaleState = new ScaleState(state, this, res);
345    }
346}
347
348