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