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