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