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