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><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