ScaleDrawable.java revision dad7d84c04c5954b63ea8bb58c52b2291f44b4df
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 com.android.internal.R; 20 21import org.xmlpull.v1.XmlPullParser; 22import org.xmlpull.v1.XmlPullParserException; 23 24import android.content.res.ColorStateList; 25import android.content.res.Resources; 26import android.content.res.TypedArray; 27import android.content.res.Resources.Theme; 28import android.graphics.*; 29import android.graphics.PorterDuff.Mode; 30import android.view.Gravity; 31import android.util.AttributeSet; 32 33import java.io.IOException; 34import java.util.Collection; 35 36/** 37 * A Drawable that changes the size of another Drawable based on its current 38 * level value. You can control how much the child Drawable changes in width 39 * and height based on the level, as well as a gravity to control where it is 40 * placed in its overall container. Most often used to implement things like 41 * progress bars. 42 * 43 * <p>It can be defined in an XML file with the <code><scale></code> element. For more 44 * information, see the guide to <a 45 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 46 * 47 * @attr ref android.R.styleable#ScaleDrawable_scaleWidth 48 * @attr ref android.R.styleable#ScaleDrawable_scaleHeight 49 * @attr ref android.R.styleable#ScaleDrawable_scaleGravity 50 * @attr ref android.R.styleable#ScaleDrawable_drawable 51 */ 52public class ScaleDrawable extends Drawable implements Drawable.Callback { 53 private ScaleState mState; 54 private boolean mMutated; 55 private final Rect mTmpRect = new Rect(); 56 57 ScaleDrawable() { 58 this(null, null); 59 } 60 61 public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) { 62 this(null, null); 63 64 mState.mDrawable = drawable; 65 mState.mGravity = gravity; 66 mState.mScaleWidth = scaleWidth; 67 mState.mScaleHeight = scaleHeight; 68 69 if (drawable != null) { 70 drawable.setCallback(this); 71 } 72 } 73 74 /** 75 * Returns the drawable scaled by this ScaleDrawable. 76 */ 77 public Drawable getDrawable() { 78 return mState.mDrawable; 79 } 80 81 private static float getPercent(TypedArray a, int name, float defaultValue) { 82 final String s = a.getString(name); 83 if (s != null) { 84 if (s.endsWith("%")) { 85 String f = s.substring(0, s.length() - 1); 86 return Float.parseFloat(f) / 100.0f; 87 } 88 } 89 return defaultValue; 90 } 91 92 @Override 93 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 94 throws XmlPullParserException, IOException { 95 super.inflate(r, parser, attrs, theme); 96 97 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable); 98 99 // Reset mDrawable to preserve old multiple-inflate behavior. This is 100 // silly, but we have CTS tests that rely on it. 101 mState.mDrawable = null; 102 103 updateStateFromTypedArray(a); 104 inflateChildElements(r, parser, attrs, theme); 105 verifyRequiredAttributes(a); 106 a.recycle(); 107 } 108 109 @Override 110 public void applyTheme(Theme t) { 111 super.applyTheme(t); 112 113 final ScaleState state = mState; 114 if (state == null) { 115 return; 116 } 117 118 if (state.mThemeAttrs != null) { 119 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ScaleDrawable); 120 try { 121 updateStateFromTypedArray(a); 122 verifyRequiredAttributes(a); 123 } catch (XmlPullParserException e) { 124 throw new RuntimeException(e); 125 } finally { 126 a.recycle(); 127 } 128 } 129 130 if (state.mDrawable != null && state.mDrawable.canApplyTheme()) { 131 state.mDrawable.applyTheme(t); 132 } 133 } 134 135 private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, 136 Theme theme) throws XmlPullParserException, IOException { 137 Drawable dr = null; 138 int type; 139 final int outerDepth = parser.getDepth(); 140 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 141 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 142 if (type != XmlPullParser.START_TAG) { 143 continue; 144 } 145 dr = Drawable.createFromXmlInner(r, parser, attrs, theme); 146 } 147 148 if (dr != null) { 149 mState.mDrawable = dr; 150 dr.setCallback(this); 151 } 152 } 153 154 private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { 155 // If we're not waiting on a theme, verify required attributes. 156 if (mState.mDrawable == null && (mState.mThemeAttrs == null 157 || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) { 158 throw new XmlPullParserException(a.getPositionDescription() 159 + ": <scale> tag requires a 'drawable' attribute or " 160 + "child tag defining a drawable"); 161 } 162 } 163 164 private void updateStateFromTypedArray(TypedArray a) { 165 final ScaleState state = mState; 166 167 // Account for any configuration changes. 168 state.mChangingConfigurations |= a.getChangingConfigurations(); 169 170 // Extract the theme attributes, if any. 171 state.mThemeAttrs = a.extractThemeAttrs(); 172 173 state.mScaleWidth = getPercent( 174 a, R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth); 175 state.mScaleHeight = getPercent( 176 a, R.styleable.ScaleDrawable_scaleHeight, state.mScaleHeight); 177 state.mGravity = a.getInt(R.styleable.ScaleDrawable_scaleGravity, state.mGravity); 178 state.mUseIntrinsicSizeAsMin = a.getBoolean( 179 R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin); 180 181 final Drawable dr = a.getDrawable(R.styleable.ScaleDrawable_drawable); 182 if (dr != null) { 183 state.mDrawable = dr; 184 dr.setCallback(this); 185 } 186 } 187 188 @Override 189 public boolean canApplyTheme() { 190 return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); 191 } 192 193 // overrides from Drawable.Callback 194 195 public void invalidateDrawable(Drawable who) { 196 if (getCallback() != null) { 197 getCallback().invalidateDrawable(this); 198 } 199 } 200 201 public void scheduleDrawable(Drawable who, Runnable what, long when) { 202 if (getCallback() != null) { 203 getCallback().scheduleDrawable(this, what, when); 204 } 205 } 206 207 public void unscheduleDrawable(Drawable who, Runnable what) { 208 if (getCallback() != null) { 209 getCallback().unscheduleDrawable(this, what); 210 } 211 } 212 213 // overrides from Drawable 214 215 @Override 216 public void draw(Canvas canvas) { 217 if (mState.mDrawable.getLevel() != 0) 218 mState.mDrawable.draw(canvas); 219 } 220 221 @Override 222 public int getChangingConfigurations() { 223 return super.getChangingConfigurations() 224 | mState.mChangingConfigurations 225 | mState.mDrawable.getChangingConfigurations(); 226 } 227 228 @Override 229 public boolean getPadding(Rect padding) { 230 // XXX need to adjust padding! 231 return mState.mDrawable.getPadding(padding); 232 } 233 234 @Override 235 public boolean setVisible(boolean visible, boolean restart) { 236 mState.mDrawable.setVisible(visible, restart); 237 return super.setVisible(visible, restart); 238 } 239 240 @Override 241 public void setAlpha(int alpha) { 242 mState.mDrawable.setAlpha(alpha); 243 } 244 245 @Override 246 public int getAlpha() { 247 return mState.mDrawable.getAlpha(); 248 } 249 250 @Override 251 public void setColorFilter(ColorFilter cf) { 252 mState.mDrawable.setColorFilter(cf); 253 } 254 255 @Override 256 public void setTintList(ColorStateList tint) { 257 mState.mDrawable.setTintList(tint); 258 } 259 260 @Override 261 public void setTintMode(Mode tintMode) { 262 mState.mDrawable.setTintMode(tintMode); 263 } 264 265 @Override 266 public int getOpacity() { 267 return mState.mDrawable.getOpacity(); 268 } 269 270 @Override 271 public boolean isStateful() { 272 return mState.mDrawable.isStateful(); 273 } 274 275 @Override 276 protected boolean onStateChange(int[] state) { 277 boolean changed = mState.mDrawable.setState(state); 278 onBoundsChange(getBounds()); 279 return changed; 280 } 281 282 @Override 283 protected boolean onLevelChange(int level) { 284 mState.mDrawable.setLevel(level); 285 onBoundsChange(getBounds()); 286 invalidateSelf(); 287 return true; 288 } 289 290 @Override 291 protected void onBoundsChange(Rect bounds) { 292 final Rect r = mTmpRect; 293 final boolean min = mState.mUseIntrinsicSizeAsMin; 294 int level = getLevel(); 295 int w = bounds.width(); 296 if (mState.mScaleWidth > 0) { 297 final int iw = min ? mState.mDrawable.getIntrinsicWidth() : 0; 298 w -= (int) ((w - iw) * (10000 - level) * mState.mScaleWidth / 10000); 299 } 300 int h = bounds.height(); 301 if (mState.mScaleHeight > 0) { 302 final int ih = min ? mState.mDrawable.getIntrinsicHeight() : 0; 303 h -= (int) ((h - ih) * (10000 - level) * mState.mScaleHeight / 10000); 304 } 305 final int layoutDirection = getLayoutDirection(); 306 Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection); 307 308 if (w > 0 && h > 0) { 309 mState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom); 310 } 311 } 312 313 @Override 314 public int getIntrinsicWidth() { 315 return mState.mDrawable.getIntrinsicWidth(); 316 } 317 318 @Override 319 public int getIntrinsicHeight() { 320 return mState.mDrawable.getIntrinsicHeight(); 321 } 322 323 @Override 324 public ConstantState getConstantState() { 325 if (mState.canConstantState()) { 326 mState.mChangingConfigurations = getChangingConfigurations(); 327 return mState; 328 } 329 return null; 330 } 331 332 @Override 333 public Drawable mutate() { 334 if (!mMutated && super.mutate() == this) { 335 mState.mDrawable.mutate(); 336 mMutated = true; 337 } 338 return this; 339 } 340 341 /** 342 * @hide 343 */ 344 public void clearMutated() { 345 super.clearMutated(); 346 mState.mDrawable.clearMutated(); 347 mMutated = false; 348 } 349 350 final static class ScaleState extends ConstantState { 351 /** Constant used to disable scaling for a particular dimension. */ 352 private static final float DO_NOT_SCALE = -1.0f; 353 354 int[] mThemeAttrs; 355 int mChangingConfigurations; 356 357 Drawable mDrawable; 358 359 float mScaleWidth = DO_NOT_SCALE; 360 float mScaleHeight = DO_NOT_SCALE; 361 int mGravity = Gravity.LEFT; 362 boolean mUseIntrinsicSizeAsMin = false; 363 364 private boolean mCheckedConstantState; 365 private boolean mCanConstantState; 366 367 ScaleState(ScaleState orig, ScaleDrawable owner, Resources res) { 368 if (orig != null) { 369 mThemeAttrs = orig.mThemeAttrs; 370 mChangingConfigurations = orig.mChangingConfigurations; 371 if (res != null) { 372 mDrawable = orig.mDrawable.getConstantState().newDrawable(res); 373 } else { 374 mDrawable = orig.mDrawable.getConstantState().newDrawable(); 375 } 376 mDrawable.setCallback(owner); 377 mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); 378 mDrawable.setBounds(orig.mDrawable.getBounds()); 379 mDrawable.setLevel(orig.mDrawable.getLevel()); 380 mScaleWidth = orig.mScaleWidth; 381 mScaleHeight = orig.mScaleHeight; 382 mGravity = orig.mGravity; 383 mUseIntrinsicSizeAsMin = orig.mUseIntrinsicSizeAsMin; 384 mCheckedConstantState = mCanConstantState = true; 385 } 386 } 387 388 @Override 389 public boolean canApplyTheme() { 390 return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme()) 391 || super.canApplyTheme(); 392 } 393 394 @Override 395 public Drawable newDrawable() { 396 return new ScaleDrawable(this, null); 397 } 398 399 @Override 400 public Drawable newDrawable(Resources res) { 401 return new ScaleDrawable(this, res); 402 } 403 404 @Override 405 public int getChangingConfigurations() { 406 return mChangingConfigurations; 407 } 408 409 boolean canConstantState() { 410 if (!mCheckedConstantState) { 411 mCanConstantState = mDrawable.getConstantState() != null; 412 mCheckedConstantState = true; 413 } 414 415 return mCanConstantState; 416 } 417 418 @Override 419 public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { 420 final ConstantState state = mDrawable.getConstantState(); 421 if (state != null) { 422 return state.addAtlasableBitmaps(atlasList); 423 } 424 return 0; 425 } 426 } 427 428 private ScaleDrawable(ScaleState state, Resources res) { 429 mState = new ScaleState(state, this, res); 430 } 431} 432 433