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