InsetDrawable.java revision 7068c39526459c18a020e29c1ebfa6aed54e2d0f
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 Drawable dr = a.getDrawable(R.styleable.InsetDrawable_drawable); 123 if (dr != null) { 124 state.mDrawable = dr; 125 dr.setCallback(this); 126 } 127 128 state.mInsetLeft = a.getDimensionPixelOffset( 129 R.styleable.InsetDrawable_insetLeft, state.mInsetLeft); 130 state.mInsetTop = a.getDimensionPixelOffset( 131 R.styleable.InsetDrawable_insetTop, state.mInsetTop); 132 state.mInsetRight = a.getDimensionPixelOffset( 133 R.styleable.InsetDrawable_insetRight, state.mInsetRight); 134 state.mInsetBottom = a.getDimensionPixelOffset( 135 R.styleable.InsetDrawable_insetBottom, state.mInsetBottom); 136 } 137 138 @Override 139 public void applyTheme(Theme t) { 140 super.applyTheme(t); 141 142 final InsetState state = mInsetState; 143 if (state == null || state.mThemeAttrs == null) { 144 return; 145 } 146 147 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable); 148 try { 149 updateStateFromTypedArray(a); 150 } catch (XmlPullParserException e) { 151 throw new RuntimeException(e); 152 } finally { 153 a.recycle(); 154 } 155 } 156 157 @Override 158 public boolean canApplyTheme() { 159 return mInsetState != null && mInsetState.mThemeAttrs != null; 160 } 161 162 @Override 163 public void invalidateDrawable(Drawable who) { 164 final Callback callback = getCallback(); 165 if (callback != null) { 166 callback.invalidateDrawable(this); 167 } 168 } 169 170 @Override 171 public void scheduleDrawable(Drawable who, Runnable what, long when) { 172 final Callback callback = getCallback(); 173 if (callback != null) { 174 callback.scheduleDrawable(this, what, when); 175 } 176 } 177 178 @Override 179 public void unscheduleDrawable(Drawable who, Runnable what) { 180 final Callback callback = getCallback(); 181 if (callback != null) { 182 callback.unscheduleDrawable(this, what); 183 } 184 } 185 186 @Override 187 public void draw(Canvas canvas) { 188 mInsetState.mDrawable.draw(canvas); 189 } 190 191 @Override 192 public int getChangingConfigurations() { 193 return super.getChangingConfigurations() 194 | mInsetState.mChangingConfigurations 195 | mInsetState.mDrawable.getChangingConfigurations(); 196 } 197 198 @Override 199 public boolean getPadding(Rect padding) { 200 boolean pad = mInsetState.mDrawable.getPadding(padding); 201 202 padding.left += mInsetState.mInsetLeft; 203 padding.right += mInsetState.mInsetRight; 204 padding.top += mInsetState.mInsetTop; 205 padding.bottom += mInsetState.mInsetBottom; 206 207 if (pad || (mInsetState.mInsetLeft | mInsetState.mInsetRight | 208 mInsetState.mInsetTop | mInsetState.mInsetBottom) != 0) { 209 return true; 210 } else { 211 return false; 212 } 213 } 214 215 /** @hide */ 216 @Override 217 public Insets getOpticalInsets() { 218 final Insets contentInsets = super.getOpticalInsets(); 219 return Insets.of(contentInsets.left + mInsetState.mInsetLeft, 220 contentInsets.top + mInsetState.mInsetTop, 221 contentInsets.right + mInsetState.mInsetRight, 222 contentInsets.bottom + mInsetState.mInsetBottom); 223 } 224 225 @Override 226 public void setHotspot(float x, float y) { 227 mInsetState.mDrawable.setHotspot(x, y); 228 } 229 230 @Override 231 public void setHotspotBounds(int left, int top, int right, int bottom) { 232 mInsetState.mDrawable.setHotspotBounds(left, top, right, bottom); 233 } 234 235 /** @hide */ 236 @Override 237 public void getHotspotBounds(Rect outRect) { 238 mInsetState.mDrawable.getHotspotBounds(outRect); 239 } 240 241 @Override 242 public boolean setVisible(boolean visible, boolean restart) { 243 mInsetState.mDrawable.setVisible(visible, restart); 244 return super.setVisible(visible, restart); 245 } 246 247 @Override 248 public void setAlpha(int alpha) { 249 mInsetState.mDrawable.setAlpha(alpha); 250 } 251 252 @Override 253 public int getAlpha() { 254 return mInsetState.mDrawable.getAlpha(); 255 } 256 257 @Override 258 public void setColorFilter(ColorFilter cf) { 259 mInsetState.mDrawable.setColorFilter(cf); 260 } 261 262 @Override 263 public void setTint(ColorStateList tint, Mode tintMode) { 264 mInsetState.mDrawable.setTint(tint, tintMode); 265 } 266 267 /** {@hide} */ 268 @Override 269 public void setLayoutDirection(int layoutDirection) { 270 mInsetState.mDrawable.setLayoutDirection(layoutDirection); 271 } 272 273 @Override 274 public int getOpacity() { 275 return mInsetState.mDrawable.getOpacity(); 276 } 277 278 @Override 279 public boolean isStateful() { 280 return mInsetState.mDrawable.isStateful(); 281 } 282 283 @Override 284 protected boolean onStateChange(int[] state) { 285 boolean changed = mInsetState.mDrawable.setState(state); 286 onBoundsChange(getBounds()); 287 return changed; 288 } 289 290 @Override 291 protected boolean onLevelChange(int level) { 292 return mInsetState.mDrawable.setLevel(level); 293 } 294 295 @Override 296 protected void onBoundsChange(Rect bounds) { 297 final Rect r = mTmpRect; 298 r.set(bounds); 299 300 r.left += mInsetState.mInsetLeft; 301 r.top += mInsetState.mInsetTop; 302 r.right -= mInsetState.mInsetRight; 303 r.bottom -= mInsetState.mInsetBottom; 304 305 mInsetState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom); 306 } 307 308 @Override 309 public int getIntrinsicWidth() { 310 return mInsetState.mDrawable.getIntrinsicWidth(); 311 } 312 313 @Override 314 public int getIntrinsicHeight() { 315 return mInsetState.mDrawable.getIntrinsicHeight(); 316 } 317 318 @Override 319 public boolean getOutline(@NonNull Outline outline) { 320 return mInsetState.mDrawable.getOutline(outline); 321 } 322 323 @Override 324 public ConstantState getConstantState() { 325 if (mInsetState.canConstantState()) { 326 mInsetState.mChangingConfigurations = getChangingConfigurations(); 327 return mInsetState; 328 } 329 return null; 330 } 331 332 @Override 333 public Drawable mutate() { 334 if (!mMutated && super.mutate() == this) { 335 mInsetState.mDrawable.mutate(); 336 mMutated = true; 337 } 338 return this; 339 } 340 341 /** 342 * Returns the drawable wrapped by this InsetDrawable. May be null. 343 */ 344 public Drawable getDrawable() { 345 return mInsetState.mDrawable; 346 } 347 348 final static class InsetState extends ConstantState { 349 int[] mThemeAttrs; 350 int mChangingConfigurations; 351 352 Drawable mDrawable; 353 354 int mInsetLeft; 355 int mInsetTop; 356 int mInsetRight; 357 int mInsetBottom; 358 359 boolean mCheckedConstantState; 360 boolean mCanConstantState; 361 362 InsetState(InsetState orig, InsetDrawable owner, Resources res) { 363 if (orig != null) { 364 mThemeAttrs = orig.mThemeAttrs; 365 mChangingConfigurations = orig.mChangingConfigurations; 366 if (res != null) { 367 mDrawable = orig.mDrawable.getConstantState().newDrawable(res); 368 } else { 369 mDrawable = orig.mDrawable.getConstantState().newDrawable(); 370 } 371 mDrawable.setCallback(owner); 372 mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); 373 mInsetLeft = orig.mInsetLeft; 374 mInsetTop = orig.mInsetTop; 375 mInsetRight = orig.mInsetRight; 376 mInsetBottom = orig.mInsetBottom; 377 mCheckedConstantState = mCanConstantState = true; 378 } 379 } 380 381 @Override 382 public Drawable newDrawable() { 383 return new InsetDrawable(this, null); 384 } 385 386 @Override 387 public Drawable newDrawable(Resources res) { 388 return new InsetDrawable(this, res); 389 } 390 391 @Override 392 public int getChangingConfigurations() { 393 return mChangingConfigurations; 394 } 395 396 boolean canConstantState() { 397 if (!mCheckedConstantState) { 398 mCanConstantState = mDrawable.getConstantState() != null; 399 mCheckedConstantState = true; 400 } 401 402 return mCanConstantState; 403 } 404 } 405 406 private InsetDrawable(InsetState state, Resources res) { 407 mInsetState = new InsetState(state, this, res); 408 } 409} 410 411