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