InsetDrawable.java revision 7d6854a2292df70de284b490348770ad7c66d47b
1/* 2 * Copyright (C) 2008 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 android.annotation.NonNull; 22 23import org.xmlpull.v1.XmlPullParser; 24import org.xmlpull.v1.XmlPullParserException; 25 26import android.content.res.ColorStateList; 27import android.content.res.Resources; 28import android.content.res.TypedArray; 29import android.content.res.Resources.Theme; 30import android.graphics.Bitmap; 31import android.graphics.Canvas; 32import android.graphics.ColorFilter; 33import android.graphics.Insets; 34import android.graphics.Outline; 35import android.graphics.PixelFormat; 36import android.graphics.PorterDuff.Mode; 37import android.graphics.Rect; 38import android.util.AttributeSet; 39 40import java.io.IOException; 41import java.util.Collection; 42 43/** 44 * A Drawable that insets another Drawable by a specified distance. 45 * This is used when a View needs a background that is smaller than 46 * the View's actual bounds. 47 * 48 * <p>It can be defined in an XML file with the <code><inset></code> element. For more 49 * information, see the guide to <a 50 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 51 * 52 * @attr ref android.R.styleable#InsetDrawable_visible 53 * @attr ref android.R.styleable#InsetDrawable_drawable 54 * @attr ref android.R.styleable#InsetDrawable_insetLeft 55 * @attr ref android.R.styleable#InsetDrawable_insetRight 56 * @attr ref android.R.styleable#InsetDrawable_insetTop 57 * @attr ref android.R.styleable#InsetDrawable_insetBottom 58 */ 59public class InsetDrawable extends Drawable implements Drawable.Callback { 60 private final Rect mTmpRect = new Rect(); 61 62 private InsetState mState; 63 private Drawable mDrawable; 64 65 private boolean mMutated; 66 67 /** 68 * No-arg constructor used by drawable inflation. 69 */ 70 InsetDrawable() { 71 this(new InsetState(), null); 72 } 73 74 /** 75 * Creates a new inset drawable with the specified inset. 76 * 77 * @param drawable The drawable to inset. 78 * @param inset Inset in pixels around the drawable. 79 */ 80 public InsetDrawable(Drawable drawable, int inset) { 81 this(drawable, inset, inset, inset, inset); 82 } 83 84 /** 85 * Creates a new inset drawable with the specified insets. 86 * 87 * @param drawable The drawable to inset. 88 * @param insetLeft Left inset in pixels. 89 * @param insetTop Top inset in pixels. 90 * @param insetRight Right inset in pixels. 91 * @param insetBottom Bottom inset in pixels. 92 */ 93 public InsetDrawable(Drawable drawable, int insetLeft, int insetTop,int insetRight, 94 int insetBottom) { 95 this(new InsetState(), null); 96 97 mState.mDrawableState = drawable == null ? null : drawable.getConstantState(); 98 mState.mInsetLeft = insetLeft; 99 mState.mInsetTop = insetTop; 100 mState.mInsetRight = insetRight; 101 mState.mInsetBottom = insetBottom; 102 103 mDrawable = drawable; 104 105 if (drawable != null) { 106 drawable.setCallback(this); 107 } 108 } 109 110 @Override 111 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 112 throws XmlPullParserException, IOException { 113 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable); 114 super.inflateWithAttributes(r, parser, a, R.styleable.InsetDrawable_visible); 115 116 updateStateFromTypedArray(a); 117 inflateChildElements(r, parser, attrs, theme); 118 verifyRequiredAttributes(a); 119 a.recycle(); 120 } 121 122 private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, 123 Theme theme) throws XmlPullParserException, IOException { 124 // Load inner XML elements. 125 if (mDrawable == null) { 126 int type; 127 while ((type=parser.next()) == XmlPullParser.TEXT) { 128 } 129 if (type != XmlPullParser.START_TAG) { 130 throw new XmlPullParserException(parser.getPositionDescription() 131 + ": <inset> tag requires a 'drawable' attribute or " 132 + "child tag defining a drawable"); 133 } 134 135 final Drawable dr = Drawable.createFromXmlInner(r, parser, attrs, theme); 136 mState.mDrawableState = dr.getConstantState(); 137 mDrawable = dr; 138 dr.setCallback(this); 139 } 140 } 141 142 private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { 143 // If we're not waiting on a theme, verify required attributes. 144 if (mDrawable == null && (mState.mThemeAttrs == null 145 || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) { 146 throw new XmlPullParserException(a.getPositionDescription() 147 + ": <inset> tag requires a 'drawable' attribute or " 148 + "child tag defining a drawable"); 149 } 150 } 151 152 private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { 153 final InsetState state = mState; 154 155 // Account for any configuration changes. 156 state.mChangingConfigurations |= a.getChangingConfigurations(); 157 158 // Extract the theme attributes, if any. 159 state.mThemeAttrs = a.extractThemeAttrs(); 160 161 final int N = a.getIndexCount(); 162 for (int i = 0; i < N; i++) { 163 final int attr = a.getIndex(i); 164 switch (attr) { 165 case R.styleable.InsetDrawable_drawable: 166 final Drawable dr = a.getDrawable(attr); 167 if (dr != null) { 168 mState.mDrawableState = dr.getConstantState(); 169 mDrawable = dr; 170 dr.setCallback(this); 171 } 172 break; 173 case R.styleable.InsetDrawable_inset: 174 final int inset = a.getDimensionPixelOffset(attr, Integer.MIN_VALUE); 175 if (inset != Integer.MIN_VALUE) { 176 state.mInsetLeft = inset; 177 state.mInsetTop = inset; 178 state.mInsetRight = inset; 179 state.mInsetBottom = inset; 180 } 181 break; 182 case R.styleable.InsetDrawable_insetLeft: 183 state.mInsetLeft = a.getDimensionPixelOffset(attr, state.mInsetLeft); 184 break; 185 case R.styleable.InsetDrawable_insetTop: 186 state.mInsetTop = a.getDimensionPixelOffset(attr, state.mInsetTop); 187 break; 188 case R.styleable.InsetDrawable_insetRight: 189 state.mInsetRight = a.getDimensionPixelOffset(attr, state.mInsetRight); 190 break; 191 case R.styleable.InsetDrawable_insetBottom: 192 state.mInsetBottom = a.getDimensionPixelOffset(attr, state.mInsetBottom); 193 break; 194 } 195 } 196 } 197 198 @Override 199 public void applyTheme(Theme t) { 200 super.applyTheme(t); 201 202 final InsetState state = mState; 203 if (state == null) { 204 return; 205 } 206 207 if (state.mThemeAttrs != null) { 208 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable); 209 try { 210 updateStateFromTypedArray(a); 211 verifyRequiredAttributes(a); 212 } catch (XmlPullParserException e) { 213 throw new RuntimeException(e); 214 } finally { 215 a.recycle(); 216 } 217 } 218 219 if (mDrawable != null && mDrawable.canApplyTheme()) { 220 mDrawable.applyTheme(t); 221 } 222 } 223 224 @Override 225 public boolean canApplyTheme() { 226 return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); 227 } 228 229 @Override 230 public void invalidateDrawable(Drawable who) { 231 final Callback callback = getCallback(); 232 if (callback != null) { 233 callback.invalidateDrawable(this); 234 } 235 } 236 237 @Override 238 public void scheduleDrawable(Drawable who, Runnable what, long when) { 239 final Callback callback = getCallback(); 240 if (callback != null) { 241 callback.scheduleDrawable(this, what, when); 242 } 243 } 244 245 @Override 246 public void unscheduleDrawable(Drawable who, Runnable what) { 247 final Callback callback = getCallback(); 248 if (callback != null) { 249 callback.unscheduleDrawable(this, what); 250 } 251 } 252 253 @Override 254 public void draw(Canvas canvas) { 255 mDrawable.draw(canvas); 256 } 257 258 @Override 259 public int getChangingConfigurations() { 260 return super.getChangingConfigurations() 261 | mState.mChangingConfigurations 262 | mDrawable.getChangingConfigurations(); 263 } 264 265 @Override 266 public boolean getPadding(Rect padding) { 267 final boolean pad = mDrawable.getPadding(padding); 268 269 padding.left += mState.mInsetLeft; 270 padding.right += mState.mInsetRight; 271 padding.top += mState.mInsetTop; 272 padding.bottom += mState.mInsetBottom; 273 274 return pad || (mState.mInsetLeft | mState.mInsetRight 275 | mState.mInsetTop | mState.mInsetBottom) != 0; 276 } 277 278 /** @hide */ 279 @Override 280 public Insets getOpticalInsets() { 281 final Insets contentInsets = super.getOpticalInsets(); 282 return Insets.of(contentInsets.left + mState.mInsetLeft, 283 contentInsets.top + mState.mInsetTop, 284 contentInsets.right + mState.mInsetRight, 285 contentInsets.bottom + mState.mInsetBottom); 286 } 287 288 @Override 289 public void setHotspot(float x, float y) { 290 mDrawable.setHotspot(x, y); 291 } 292 293 @Override 294 public void setHotspotBounds(int left, int top, int right, int bottom) { 295 mDrawable.setHotspotBounds(left, top, right, bottom); 296 } 297 298 /** @hide */ 299 @Override 300 public void getHotspotBounds(Rect outRect) { 301 mDrawable.getHotspotBounds(outRect); 302 } 303 304 @Override 305 public boolean setVisible(boolean visible, boolean restart) { 306 mDrawable.setVisible(visible, restart); 307 return super.setVisible(visible, restart); 308 } 309 310 @Override 311 public void setAlpha(int alpha) { 312 mDrawable.setAlpha(alpha); 313 } 314 315 @Override 316 public int getAlpha() { 317 return mDrawable.getAlpha(); 318 } 319 320 @Override 321 public void setColorFilter(ColorFilter cf) { 322 mDrawable.setColorFilter(cf); 323 } 324 325 @Override 326 public void setTintList(ColorStateList tint) { 327 mDrawable.setTintList(tint); 328 } 329 330 @Override 331 public void setTintMode(Mode tintMode) { 332 mDrawable.setTintMode(tintMode); 333 } 334 335 /** {@hide} */ 336 @Override 337 public void setLayoutDirection(int layoutDirection) { 338 mDrawable.setLayoutDirection(layoutDirection); 339 } 340 341 @Override 342 public int getOpacity() { 343 final InsetState state = mState; 344 final int opacity = mDrawable.getOpacity(); 345 if (opacity == PixelFormat.OPAQUE && (state.mInsetLeft > 0 || state.mInsetTop > 0 346 || state.mInsetRight > 0 || state.mInsetBottom > 0)) { 347 return PixelFormat.TRANSLUCENT; 348 } 349 return opacity; 350 } 351 352 @Override 353 public boolean isStateful() { 354 return mDrawable.isStateful(); 355 } 356 357 @Override 358 protected boolean onStateChange(int[] state) { 359 final boolean changed = mDrawable.setState(state); 360 onBoundsChange(getBounds()); 361 return changed; 362 } 363 364 @Override 365 protected boolean onLevelChange(int level) { 366 return mDrawable.setLevel(level); 367 } 368 369 @Override 370 protected void onBoundsChange(Rect bounds) { 371 final Rect r = mTmpRect; 372 r.set(bounds); 373 374 r.left += mState.mInsetLeft; 375 r.top += mState.mInsetTop; 376 r.right -= mState.mInsetRight; 377 r.bottom -= mState.mInsetBottom; 378 379 mDrawable.setBounds(r.left, r.top, r.right, r.bottom); 380 } 381 382 @Override 383 public int getIntrinsicWidth() { 384 return mDrawable.getIntrinsicWidth() + mState.mInsetLeft + mState.mInsetRight; 385 } 386 387 @Override 388 public int getIntrinsicHeight() { 389 return mDrawable.getIntrinsicHeight() + mState.mInsetTop + mState.mInsetBottom; 390 } 391 392 @Override 393 public void getOutline(@NonNull Outline outline) { 394 mDrawable.getOutline(outline); 395 } 396 397 @Override 398 public ConstantState getConstantState() { 399 if (mState.canConstantState()) { 400 mState.mChangingConfigurations = getChangingConfigurations(); 401 return mState; 402 } 403 return null; 404 } 405 406 @Override 407 public Drawable mutate() { 408 if (!mMutated && super.mutate() == this) { 409 mState = new InsetState(mState); 410 mDrawable.mutate(); 411 mState.mDrawableState = mDrawable.getConstantState(); 412 mMutated = true; 413 } 414 return this; 415 } 416 417 /** 418 * @hide 419 */ 420 public void clearMutated() { 421 super.clearMutated(); 422 mDrawable.clearMutated(); 423 mMutated = false; 424 } 425 426 /** 427 * Returns the drawable wrapped by this InsetDrawable. May be null. 428 */ 429 public Drawable getDrawable() { 430 return mDrawable; 431 } 432 433 private static final class InsetState extends ConstantState { 434 int[] mThemeAttrs; 435 int mChangingConfigurations; 436 437 ConstantState mDrawableState; 438 439 int mInsetLeft = 0; 440 int mInsetTop = 0; 441 int mInsetRight = 0; 442 int mInsetBottom = 0; 443 444 public InsetState() { 445 // Empty constructor. 446 } 447 448 public InsetState(InsetState orig) { 449 if (orig != null) { 450 mThemeAttrs = orig.mThemeAttrs; 451 mChangingConfigurations = orig.mChangingConfigurations; 452 mDrawableState = orig.mDrawableState; 453 mInsetLeft = orig.mInsetLeft; 454 mInsetTop = orig.mInsetTop; 455 mInsetRight = orig.mInsetRight; 456 mInsetBottom = orig.mInsetBottom; 457 } 458 } 459 460 @Override 461 public boolean canApplyTheme() { 462 return mThemeAttrs != null 463 || (mDrawableState != null && mDrawableState.canApplyTheme()) 464 || super.canApplyTheme(); 465 } 466 467 @Override 468 public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { 469 final ConstantState state = mDrawableState; 470 if (state != null) { 471 return state.addAtlasableBitmaps(atlasList); 472 } 473 return 0; 474 } 475 476 @Override 477 public Drawable newDrawable() { 478 return new InsetDrawable(this, null); 479 } 480 481 @Override 482 public Drawable newDrawable(Resources res) { 483 return new InsetDrawable(this, res); 484 } 485 486 @Override 487 public int getChangingConfigurations() { 488 return mChangingConfigurations; 489 } 490 491 public boolean canConstantState() { 492 return mDrawableState != null; 493 } 494 } 495 496 /** 497 * The one constructor to rule them all. This is called by all public 498 * constructors to set the state and initialize local properties. 499 */ 500 private InsetDrawable(InsetState state, Resources res) { 501 mState = state; 502 503 updateLocalState(res); 504 } 505 506 /** 507 * Initializes local dynamic properties from state. This should be called 508 * after significant state changes, e.g. from the One True Constructor and 509 * after inflating or applying a theme. 510 */ 511 private void updateLocalState(Resources res) { 512 if (mState.mDrawableState != null) { 513 mDrawable = mState.mDrawableState.newDrawable(res); 514 } 515 } 516} 517 518