ScaleDrawable.java revision c0053223bedf33581b0830fb87be32c1f26e5372
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.graphics.*;
25import android.view.Gravity;
26import android.util.AttributeSet;
27import android.view.View;
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)
89            throws XmlPullParserException, IOException {
90        super.inflate(r, parser, attrs);
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);
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 void setColorFilter(ColorFilter cf) {
182        mScaleState.mDrawable.setColorFilter(cf);
183    }
184
185    @Override
186    public int getOpacity() {
187        return mScaleState.mDrawable.getOpacity();
188    }
189
190    @Override
191    public boolean isStateful() {
192        return mScaleState.mDrawable.isStateful();
193    }
194
195    @Override
196    protected boolean onStateChange(int[] state) {
197        boolean changed = mScaleState.mDrawable.setState(state);
198        onBoundsChange(getBounds());
199        return changed;
200    }
201
202    @Override
203    protected boolean onLevelChange(int level) {
204        mScaleState.mDrawable.setLevel(level);
205        onBoundsChange(getBounds());
206        invalidateSelf();
207        return true;
208    }
209
210    @Override
211    protected void onBoundsChange(Rect bounds) {
212        final Rect r = mTmpRect;
213        final boolean min = mScaleState.mUseIntrinsicSizeAsMin;
214        int level = getLevel();
215        int w = bounds.width();
216        if (mScaleState.mScaleWidth > 0) {
217            final int iw = min ? mScaleState.mDrawable.getIntrinsicWidth() : 0;
218            w -= (int) ((w - iw) * (10000 - level) * mScaleState.mScaleWidth / 10000);
219        }
220        int h = bounds.height();
221        if (mScaleState.mScaleHeight > 0) {
222            final int ih = min ? mScaleState.mDrawable.getIntrinsicHeight() : 0;
223            h -= (int) ((h - ih) * (10000 - level) * mScaleState.mScaleHeight / 10000);
224        }
225        final int layoutDirection = getResolvedLayoutDirectionSelf();
226        Gravity.apply(mScaleState.mGravity, w, h, bounds, r, layoutDirection);
227
228        if (w > 0 && h > 0) {
229            mScaleState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom);
230        }
231    }
232
233    @Override
234    public int getIntrinsicWidth() {
235        return mScaleState.mDrawable.getIntrinsicWidth();
236    }
237
238    @Override
239    public int getIntrinsicHeight() {
240        return mScaleState.mDrawable.getIntrinsicHeight();
241    }
242
243    @Override
244    public ConstantState getConstantState() {
245        if (mScaleState.canConstantState()) {
246            mScaleState.mChangingConfigurations = getChangingConfigurations();
247            return mScaleState;
248        }
249        return null;
250    }
251
252    @Override
253    public Drawable mutate() {
254        if (!mMutated && super.mutate() == this) {
255            mScaleState.mDrawable.mutate();
256            mMutated = true;
257        }
258        return this;
259    }
260
261    final static class ScaleState extends ConstantState {
262        Drawable mDrawable;
263        int mChangingConfigurations;
264        float mScaleWidth;
265        float mScaleHeight;
266        int mGravity;
267        boolean mUseIntrinsicSizeAsMin;
268
269        private boolean mCheckedConstantState;
270        private boolean mCanConstantState;
271
272        ScaleState(ScaleState orig, ScaleDrawable owner, Resources res) {
273            if (orig != null) {
274                if (res != null) {
275                    mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
276                } else {
277                    mDrawable = orig.mDrawable.getConstantState().newDrawable();
278                }
279                mDrawable.setCallback(owner);
280                mScaleWidth = orig.mScaleWidth;
281                mScaleHeight = orig.mScaleHeight;
282                mGravity = orig.mGravity;
283                mUseIntrinsicSizeAsMin = orig.mUseIntrinsicSizeAsMin;
284                mCheckedConstantState = mCanConstantState = true;
285            }
286        }
287
288        @Override
289        public Drawable newDrawable() {
290            return new ScaleDrawable(this, null);
291        }
292
293        @Override
294        public Drawable newDrawable(Resources res) {
295            return new ScaleDrawable(this, res);
296        }
297
298        @Override
299        public int getChangingConfigurations() {
300            return mChangingConfigurations;
301        }
302
303        boolean canConstantState() {
304            if (!mCheckedConstantState) {
305                mCanConstantState = mDrawable.getConstantState() != null;
306                mCheckedConstantState = true;
307            }
308
309            return mCanConstantState;
310        }
311    }
312
313    private ScaleDrawable(ScaleState state, Resources res) {
314        mScaleState = new ScaleState(state, this, res);
315    }
316}
317
318