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 17 package android.graphics.drawable; 18 19 import com.android.internal.R; 20 21 import org.xmlpull.v1.XmlPullParser; 22 import org.xmlpull.v1.XmlPullParserException; 23 24 import android.annotation.NonNull; 25 import android.content.res.Resources; 26 import android.content.res.Resources.Theme; 27 import android.content.res.TypedArray; 28 import android.graphics.Insets; 29 import android.graphics.Outline; 30 import android.graphics.PixelFormat; 31 import android.graphics.Rect; 32 import android.util.AttributeSet; 33 34 import 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 */ 52 public class InsetDrawable extends DrawableWrapper { 53 private final Rect mTmpRect = new Rect(); 54 55 private InsetState mState; 56 57 /** 58 * No-arg constructor used by drawable inflation. 59 */ 60 InsetDrawable() { 61 this(new InsetState(null), null); 62 } 63 64 /** 65 * Creates a new inset drawable with the specified inset. 66 * 67 * @param drawable The drawable to inset. 68 * @param inset Inset in pixels around the drawable. 69 */ 70 public InsetDrawable(Drawable drawable, int inset) { 71 this(drawable, inset, inset, inset, inset); 72 } 73 74 /** 75 * Creates a new inset drawable with the specified insets. 76 * 77 * @param drawable The drawable to inset. 78 * @param insetLeft Left inset in pixels. 79 * @param insetTop Top inset in pixels. 80 * @param insetRight Right inset in pixels. 81 * @param insetBottom Bottom inset in pixels. 82 */ 83 public InsetDrawable(Drawable drawable, int insetLeft, int insetTop,int insetRight, 84 int insetBottom) { 85 this(new InsetState(null), null); 86 87 mState.mInsetLeft = insetLeft; 88 mState.mInsetTop = insetTop; 89 mState.mInsetRight = insetRight; 90 mState.mInsetBottom = insetBottom; 91 92 setDrawable(drawable); 93 } 94 95 @Override 96 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 97 throws XmlPullParserException, IOException { 98 super.inflate(r, parser, attrs, theme); 99 100 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable); 101 updateStateFromTypedArray(a); 102 inflateChildDrawable(r, parser, attrs, theme); 103 verifyRequiredAttributes(a); 104 a.recycle(); 105 } 106 107 private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { 108 // If we're not waiting on a theme, verify required attributes. 109 if (getDrawable() == null && (mState.mThemeAttrs == null 110 || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) { 111 throw new XmlPullParserException(a.getPositionDescription() 112 + ": <inset> tag requires a 'drawable' attribute or " 113 + "child tag defining a drawable"); 114 } 115 } 116 117 @Override 118 void updateStateFromTypedArray(TypedArray a) { 119 super.updateStateFromTypedArray(a); 120 121 final InsetState state = mState; 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 setDrawable(dr); 130 } 131 break; 132 case R.styleable.InsetDrawable_inset: 133 final int inset = a.getDimensionPixelOffset(attr, Integer.MIN_VALUE); 134 if (inset != Integer.MIN_VALUE) { 135 state.mInsetLeft = inset; 136 state.mInsetTop = inset; 137 state.mInsetRight = inset; 138 state.mInsetBottom = inset; 139 } 140 break; 141 case R.styleable.InsetDrawable_insetLeft: 142 state.mInsetLeft = a.getDimensionPixelOffset(attr, state.mInsetLeft); 143 break; 144 case R.styleable.InsetDrawable_insetTop: 145 state.mInsetTop = a.getDimensionPixelOffset(attr, state.mInsetTop); 146 break; 147 case R.styleable.InsetDrawable_insetRight: 148 state.mInsetRight = a.getDimensionPixelOffset(attr, state.mInsetRight); 149 break; 150 case R.styleable.InsetDrawable_insetBottom: 151 state.mInsetBottom = a.getDimensionPixelOffset(attr, state.mInsetBottom); 152 break; 153 } 154 } 155 } 156 157 @Override 158 public void applyTheme(Theme t) { 159 final InsetState state = mState; 160 if (state == null) { 161 return; 162 } 163 164 if (state.mThemeAttrs != null) { 165 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable); 166 try { 167 updateStateFromTypedArray(a); 168 verifyRequiredAttributes(a); 169 } catch (XmlPullParserException e) { 170 throw new RuntimeException(e); 171 } finally { 172 a.recycle(); 173 } 174 } 175 176 // The drawable may have changed as a result of applying the theme, so 177 // apply the theme to the wrapped drawable last. 178 super.applyTheme(t); 179 } 180 181 @Override 182 public boolean getPadding(Rect padding) { 183 final boolean pad = super.getPadding(padding); 184 185 padding.left += mState.mInsetLeft; 186 padding.right += mState.mInsetRight; 187 padding.top += mState.mInsetTop; 188 padding.bottom += mState.mInsetBottom; 189 190 return pad || (mState.mInsetLeft | mState.mInsetRight 191 | mState.mInsetTop | mState.mInsetBottom) != 0; 192 } 193 194 /** @hide */ 195 @Override 196 public Insets getOpticalInsets() { 197 final Insets contentInsets = super.getOpticalInsets(); 198 return Insets.of(contentInsets.left + mState.mInsetLeft, 199 contentInsets.top + mState.mInsetTop, 200 contentInsets.right + mState.mInsetRight, 201 contentInsets.bottom + mState.mInsetBottom); 202 } 203 204 @Override 205 public int getOpacity() { 206 final InsetState state = mState; 207 final int opacity = getDrawable().getOpacity(); 208 if (opacity == PixelFormat.OPAQUE && (state.mInsetLeft > 0 || state.mInsetTop > 0 209 || state.mInsetRight > 0 || state.mInsetBottom > 0)) { 210 return PixelFormat.TRANSLUCENT; 211 } 212 return opacity; 213 } 214 215 @Override 216 protected void onBoundsChange(Rect bounds) { 217 final Rect r = mTmpRect; 218 r.set(bounds); 219 220 r.left += mState.mInsetLeft; 221 r.top += mState.mInsetTop; 222 r.right -= mState.mInsetRight; 223 r.bottom -= mState.mInsetBottom; 224 225 // Apply inset bounds to the wrapped drawable. 226 super.onBoundsChange(r); 227 } 228 229 @Override 230 public int getIntrinsicWidth() { 231 return getDrawable().getIntrinsicWidth() + mState.mInsetLeft + mState.mInsetRight; 232 } 233 234 @Override 235 public int getIntrinsicHeight() { 236 return getDrawable().getIntrinsicHeight() + mState.mInsetTop + mState.mInsetBottom; 237 } 238 239 @Override 240 public void getOutline(@NonNull Outline outline) { 241 getDrawable().getOutline(outline); 242 } 243 244 @Override 245 DrawableWrapperState mutateConstantState() { 246 mState = new InsetState(mState); 247 return mState; 248 } 249 250 static final class InsetState extends DrawableWrapper.DrawableWrapperState { 251 int mInsetLeft = 0; 252 int mInsetTop = 0; 253 int mInsetRight = 0; 254 int mInsetBottom = 0; 255 256 InsetState(InsetState orig) { 257 super(orig); 258 259 if (orig != null) { 260 mInsetLeft = orig.mInsetLeft; 261 mInsetTop = orig.mInsetTop; 262 mInsetRight = orig.mInsetRight; 263 mInsetBottom = orig.mInsetBottom; 264 } 265 } 266 267 @Override 268 public Drawable newDrawable(Resources res) { 269 return new InsetDrawable(this, res); 270 } 271 } 272 273 /** 274 * The one constructor to rule them all. This is called by all public 275 * constructors to set the state and initialize local properties. 276 */ 277 private InsetDrawable(InsetState state, Resources res) { 278 super(state, res); 279 280 mState = state; 281 } 282 } 283 284