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 org.xmlpull.v1.XmlPullParser; 22import org.xmlpull.v1.XmlPullParserException; 23 24import android.annotation.NonNull; 25import android.annotation.Nullable; 26import android.content.res.Resources; 27import android.content.res.Resources.Theme; 28import android.content.res.TypedArray; 29import android.graphics.Bitmap; 30import android.graphics.Insets; 31import android.graphics.Outline; 32import android.graphics.PixelFormat; 33import android.graphics.Rect; 34import android.util.AttributeSet; 35import android.util.DisplayMetrics; 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 DrawableWrapper { 56 private final Rect mTmpRect = new Rect(); 57 58 private InsetState mState; 59 60 /** 61 * No-arg constructor used by drawable inflation. 62 */ 63 InsetDrawable() { 64 this(new InsetState(null, null), null); 65 } 66 67 /** 68 * Creates a new inset drawable with the specified inset. 69 * 70 * @param drawable The drawable to inset. 71 * @param inset Inset in pixels around the drawable. 72 */ 73 public InsetDrawable(@Nullable Drawable drawable, int inset) { 74 this(drawable, inset, inset, inset, inset); 75 } 76 77 /** 78 * Creates a new inset drawable with the specified insets. 79 * 80 * @param drawable The drawable to inset. 81 * @param insetLeft Left inset in pixels. 82 * @param insetTop Top inset in pixels. 83 * @param insetRight Right inset in pixels. 84 * @param insetBottom Bottom inset in pixels. 85 */ 86 public InsetDrawable(@Nullable Drawable drawable, int insetLeft, int insetTop, 87 int insetRight, int insetBottom) { 88 this(new InsetState(null, null), null); 89 90 mState.mInsetLeft = insetLeft; 91 mState.mInsetTop = insetTop; 92 mState.mInsetRight = insetRight; 93 mState.mInsetBottom = insetBottom; 94 95 setDrawable(drawable); 96 } 97 98 @Override 99 public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, 100 @NonNull AttributeSet attrs, @Nullable Theme theme) 101 throws XmlPullParserException, IOException { 102 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable); 103 104 // Inflation will advance the XmlPullParser and AttributeSet. 105 super.inflate(r, parser, attrs, theme); 106 107 updateStateFromTypedArray(a); 108 verifyRequiredAttributes(a); 109 a.recycle(); 110 } 111 112 @Override 113 public void applyTheme(@NonNull Theme t) { 114 super.applyTheme(t); 115 116 final InsetState state = mState; 117 if (state == null) { 118 return; 119 } 120 121 if (state.mThemeAttrs != null) { 122 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable); 123 try { 124 updateStateFromTypedArray(a); 125 verifyRequiredAttributes(a); 126 } catch (XmlPullParserException e) { 127 rethrowAsRuntimeException(e); 128 } finally { 129 a.recycle(); 130 } 131 } 132 } 133 134 private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { 135 // If we're not waiting on a theme, verify required attributes. 136 if (getDrawable() == null && (mState.mThemeAttrs == null 137 || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) { 138 throw new XmlPullParserException(a.getPositionDescription() 139 + ": <inset> tag requires a 'drawable' attribute or " 140 + "child tag defining a drawable"); 141 } 142 } 143 144 private void updateStateFromTypedArray(@NonNull TypedArray a) { 145 final InsetState state = mState; 146 if (state == null) { 147 return; 148 } 149 150 // Account for any configuration changes. 151 state.mChangingConfigurations |= a.getChangingConfigurations(); 152 153 // Extract the theme attributes, if any. 154 state.mThemeAttrs = a.extractThemeAttrs(); 155 156 // Inset attribute may be overridden by more specific attributes. 157 if (a.hasValue(R.styleable.InsetDrawable_inset)) { 158 final int inset = a.getDimensionPixelOffset(R.styleable.InsetDrawable_inset, 0); 159 state.mInsetLeft = inset; 160 state.mInsetTop = inset; 161 state.mInsetRight = inset; 162 state.mInsetBottom = inset; 163 } 164 165 state.mInsetLeft = a.getDimensionPixelOffset( 166 R.styleable.InsetDrawable_insetLeft, state.mInsetLeft); 167 state.mInsetRight = a.getDimensionPixelOffset( 168 R.styleable.InsetDrawable_insetRight, state.mInsetRight); 169 state.mInsetTop = a.getDimensionPixelOffset( 170 R.styleable.InsetDrawable_insetTop, state.mInsetTop); 171 state.mInsetBottom = a.getDimensionPixelOffset( 172 R.styleable.InsetDrawable_insetBottom, state.mInsetBottom); 173 } 174 175 @Override 176 public boolean getPadding(Rect padding) { 177 final boolean pad = super.getPadding(padding); 178 179 padding.left += mState.mInsetLeft; 180 padding.right += mState.mInsetRight; 181 padding.top += mState.mInsetTop; 182 padding.bottom += mState.mInsetBottom; 183 184 return pad || (mState.mInsetLeft | mState.mInsetRight 185 | mState.mInsetTop | mState.mInsetBottom) != 0; 186 } 187 188 /** @hide */ 189 @Override 190 public Insets getOpticalInsets() { 191 final Insets contentInsets = super.getOpticalInsets(); 192 return Insets.of(contentInsets.left + mState.mInsetLeft, 193 contentInsets.top + mState.mInsetTop, 194 contentInsets.right + mState.mInsetRight, 195 contentInsets.bottom + mState.mInsetBottom); 196 } 197 198 @Override 199 public int getOpacity() { 200 final InsetState state = mState; 201 final int opacity = getDrawable().getOpacity(); 202 if (opacity == PixelFormat.OPAQUE && (state.mInsetLeft > 0 || state.mInsetTop > 0 203 || state.mInsetRight > 0 || state.mInsetBottom > 0)) { 204 return PixelFormat.TRANSLUCENT; 205 } 206 return opacity; 207 } 208 209 @Override 210 protected void onBoundsChange(Rect bounds) { 211 final Rect r = mTmpRect; 212 r.set(bounds); 213 214 r.left += mState.mInsetLeft; 215 r.top += mState.mInsetTop; 216 r.right -= mState.mInsetRight; 217 r.bottom -= mState.mInsetBottom; 218 219 // Apply inset bounds to the wrapped drawable. 220 super.onBoundsChange(r); 221 } 222 223 @Override 224 public int getIntrinsicWidth() { 225 final int childWidth = getDrawable().getIntrinsicWidth(); 226 if (childWidth < 0) { 227 return -1; 228 } 229 return childWidth + mState.mInsetLeft + mState.mInsetRight; 230 } 231 232 @Override 233 public int getIntrinsicHeight() { 234 final int childHeight = getDrawable().getIntrinsicHeight(); 235 if (childHeight < 0) { 236 return -1; 237 } 238 return childHeight + mState.mInsetTop + mState.mInsetBottom; 239 } 240 241 @Override 242 public void getOutline(@NonNull Outline outline) { 243 getDrawable().getOutline(outline); 244 } 245 246 @Override 247 DrawableWrapperState mutateConstantState() { 248 mState = new InsetState(mState, null); 249 return mState; 250 } 251 252 static final class InsetState extends DrawableWrapper.DrawableWrapperState { 253 private int[] mThemeAttrs; 254 255 int mInsetLeft = 0; 256 int mInsetTop = 0; 257 int mInsetRight = 0; 258 int mInsetBottom = 0; 259 260 InsetState(@Nullable InsetState orig, @Nullable Resources res) { 261 super(orig, res); 262 263 if (orig != null) { 264 mInsetLeft = orig.mInsetLeft; 265 mInsetTop = orig.mInsetTop; 266 mInsetRight = orig.mInsetRight; 267 mInsetBottom = orig.mInsetBottom; 268 269 if (orig.mDensity != mDensity) { 270 applyDensityScaling(orig.mDensity, mDensity); 271 } 272 } 273 } 274 275 @Override 276 void onDensityChanged(int sourceDensity, int targetDensity) { 277 super.onDensityChanged(sourceDensity, targetDensity); 278 279 applyDensityScaling(sourceDensity, targetDensity); 280 } 281 282 /** 283 * Called when the constant state density changes to scale 284 * density-dependent properties specific to insets. 285 * 286 * @param sourceDensity the previous constant state density 287 * @param targetDensity the new constant state density 288 */ 289 private void applyDensityScaling(int sourceDensity, int targetDensity) { 290 mInsetLeft = Bitmap.scaleFromDensity(mInsetLeft, sourceDensity, targetDensity); 291 mInsetTop = Bitmap.scaleFromDensity(mInsetTop, sourceDensity, targetDensity); 292 mInsetRight = Bitmap.scaleFromDensity(mInsetRight, sourceDensity, targetDensity); 293 mInsetBottom = Bitmap.scaleFromDensity(mInsetBottom, sourceDensity, targetDensity); 294 } 295 296 @Override 297 public Drawable newDrawable(@Nullable Resources res) { 298 // If this drawable is being created for a different density, 299 // just create a new constant state and call it a day. 300 final InsetState state; 301 if (res != null) { 302 final int densityDpi = res.getDisplayMetrics().densityDpi; 303 final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; 304 if (density != mDensity) { 305 state = new InsetState(this, res); 306 } else { 307 state = this; 308 } 309 } else { 310 state = this; 311 } 312 313 return new InsetDrawable(state, res); 314 } 315 } 316 317 /** 318 * The one constructor to rule them all. This is called by all public 319 * constructors to set the state and initialize local properties. 320 */ 321 private InsetDrawable(@NonNull InsetState state, @Nullable Resources res) { 322 super(state, res); 323 324 mState = state; 325 } 326} 327 328