1 /* 2 * Copyright (C) 2006 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.annotation.Nullable; 26 import android.content.res.Resources; 27 import android.content.res.Resources.Theme; 28 import android.content.res.TypedArray; 29 import android.graphics.Canvas; 30 import android.graphics.PixelFormat; 31 import android.graphics.Rect; 32 import android.util.AttributeSet; 33 import android.util.TypedValue; 34 import android.view.Gravity; 35 36 import java.io.IOException; 37 38 /** 39 * A Drawable that changes the size of another Drawable based on its current 40 * level value. You can control how much the child Drawable changes in width 41 * and height based on the level, as well as a gravity to control where it is 42 * placed in its overall container. Most often used to implement things like 43 * progress bars. 44 * <p> 45 * The default level may be specified from XML using the 46 * {@link android.R.styleable#ScaleDrawable_level android:level} property. When 47 * this property is not specified, the default level is 0, which corresponds to 48 * zero height and/or width depending on the values specified for 49 * {@code android.R.styleable#ScaleDrawable_scaleWidth scaleWidth} and 50 * {@code android.R.styleable#ScaleDrawable_scaleHeight scaleHeight}. At run 51 * time, the level may be set via {@link #setLevel(int)}. 52 * <p> 53 * A scale drawable may be defined in an XML file with the {@code <scale>} 54 * element. For more information, see the guide to 55 * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable 56 * Resources</a>. 57 * 58 * @attr ref android.R.styleable#ScaleDrawable_scaleWidth 59 * @attr ref android.R.styleable#ScaleDrawable_scaleHeight 60 * @attr ref android.R.styleable#ScaleDrawable_scaleGravity 61 * @attr ref android.R.styleable#ScaleDrawable_drawable 62 * @attr ref android.R.styleable#ScaleDrawable_level 63 */ 64 public class ScaleDrawable extends DrawableWrapper { 65 private static final int MAX_LEVEL = 10000; 66 67 private final Rect mTmpRect = new Rect(); 68 69 private ScaleState mState; 70 ScaleDrawable()71 ScaleDrawable() { 72 this(new ScaleState(null, null), null); 73 } 74 75 /** 76 * Creates a new scale drawable with the specified gravity and scale 77 * properties. 78 * 79 * @param drawable the drawable to scale 80 * @param gravity gravity constant (see {@link Gravity} used to position 81 * the scaled drawable within the parent container 82 * @param scaleWidth width scaling factor [0...1] to use then the level is 83 * at the maximum value, or -1 to not scale width 84 * @param scaleHeight height scaling factor [0...1] to use then the level 85 * is at the maximum value, or -1 to not scale height 86 */ ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight)87 public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) { 88 this(new ScaleState(null, null), null); 89 90 mState.mGravity = gravity; 91 mState.mScaleWidth = scaleWidth; 92 mState.mScaleHeight = scaleHeight; 93 94 setDrawable(drawable); 95 } 96 97 @Override inflate(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)98 public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, 99 @NonNull AttributeSet attrs, @Nullable Theme theme) 100 throws XmlPullParserException, IOException { 101 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable); 102 103 // Inflation will advance the XmlPullParser and AttributeSet. 104 super.inflate(r, parser, attrs, theme); 105 106 updateStateFromTypedArray(a); 107 verifyRequiredAttributes(a); 108 a.recycle(); 109 110 updateLocalState(); 111 } 112 113 @Override applyTheme(@onNull Theme t)114 public void applyTheme(@NonNull Theme t) { 115 super.applyTheme(t); 116 117 final ScaleState state = mState; 118 if (state == null) { 119 return; 120 } 121 122 if (state.mThemeAttrs != null) { 123 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ScaleDrawable); 124 try { 125 updateStateFromTypedArray(a); 126 verifyRequiredAttributes(a); 127 } catch (XmlPullParserException e) { 128 rethrowAsRuntimeException(e); 129 } finally { 130 a.recycle(); 131 } 132 } 133 134 updateLocalState(); 135 } 136 verifyRequiredAttributes(@onNull TypedArray a)137 private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { 138 // If we're not waiting on a theme, verify required attributes. 139 if (getDrawable() == null && (mState.mThemeAttrs == null 140 || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) { 141 throw new XmlPullParserException(a.getPositionDescription() 142 + ": <scale> tag requires a 'drawable' attribute or " 143 + "child tag defining a drawable"); 144 } 145 } 146 updateStateFromTypedArray(@onNull TypedArray a)147 private void updateStateFromTypedArray(@NonNull TypedArray a) { 148 final ScaleState state = mState; 149 if (state == null) { 150 return; 151 } 152 153 // Account for any configuration changes. 154 state.mChangingConfigurations |= a.getChangingConfigurations(); 155 156 // Extract the theme attributes, if any. 157 state.mThemeAttrs = a.extractThemeAttrs(); 158 159 state.mScaleWidth = getPercent(a, 160 R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth); 161 state.mScaleHeight = getPercent(a, 162 R.styleable.ScaleDrawable_scaleHeight, state.mScaleHeight); 163 state.mGravity = a.getInt( 164 R.styleable.ScaleDrawable_scaleGravity, state.mGravity); 165 state.mUseIntrinsicSizeAsMin = a.getBoolean( 166 R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin); 167 state.mInitialLevel = a.getInt( 168 R.styleable.ScaleDrawable_level, state.mInitialLevel); 169 } 170 getPercent(TypedArray a, int index, float defaultValue)171 private static float getPercent(TypedArray a, int index, float defaultValue) { 172 final int type = a.getType(index); 173 if (type == TypedValue.TYPE_FRACTION || type == TypedValue.TYPE_NULL) { 174 return a.getFraction(index, 1, 1, defaultValue); 175 } 176 177 // Coerce to float. 178 final String s = a.getString(index); 179 if (s != null) { 180 if (s.endsWith("%")) { 181 final String f = s.substring(0, s.length() - 1); 182 return Float.parseFloat(f) / 100.0f; 183 } 184 } 185 186 return defaultValue; 187 } 188 189 @Override draw(Canvas canvas)190 public void draw(Canvas canvas) { 191 final Drawable d = getDrawable(); 192 if (d != null && d.getLevel() != 0) { 193 d.draw(canvas); 194 } 195 } 196 197 @Override getOpacity()198 public int getOpacity() { 199 final Drawable d = getDrawable(); 200 if (d.getLevel() == 0) { 201 return PixelFormat.TRANSPARENT; 202 } 203 204 final int opacity = d.getOpacity(); 205 if (opacity == PixelFormat.OPAQUE && d.getLevel() < MAX_LEVEL) { 206 return PixelFormat.TRANSLUCENT; 207 } 208 209 return opacity; 210 } 211 212 @Override onLevelChange(int level)213 protected boolean onLevelChange(int level) { 214 super.onLevelChange(level); 215 onBoundsChange(getBounds()); 216 invalidateSelf(); 217 return true; 218 } 219 220 @Override onBoundsChange(Rect bounds)221 protected void onBoundsChange(Rect bounds) { 222 final Drawable d = getDrawable(); 223 final Rect r = mTmpRect; 224 final boolean min = mState.mUseIntrinsicSizeAsMin; 225 final int level = getLevel(); 226 227 int w = bounds.width(); 228 if (mState.mScaleWidth > 0) { 229 final int iw = min ? d.getIntrinsicWidth() : 0; 230 w -= (int) ((w - iw) * (MAX_LEVEL - level) * mState.mScaleWidth / MAX_LEVEL); 231 } 232 233 int h = bounds.height(); 234 if (mState.mScaleHeight > 0) { 235 final int ih = min ? d.getIntrinsicHeight() : 0; 236 h -= (int) ((h - ih) * (MAX_LEVEL - level) * mState.mScaleHeight / MAX_LEVEL); 237 } 238 239 final int layoutDirection = getLayoutDirection(); 240 Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection); 241 242 if (w > 0 && h > 0) { 243 d.setBounds(r.left, r.top, r.right, r.bottom); 244 } 245 } 246 247 @Override mutateConstantState()248 DrawableWrapperState mutateConstantState() { 249 mState = new ScaleState(mState, null); 250 return mState; 251 } 252 253 static final class ScaleState extends DrawableWrapper.DrawableWrapperState { 254 /** Constant used to disable scaling for a particular dimension. */ 255 private static final float DO_NOT_SCALE = -1.0f; 256 257 private int[] mThemeAttrs; 258 259 float mScaleWidth = DO_NOT_SCALE; 260 float mScaleHeight = DO_NOT_SCALE; 261 int mGravity = Gravity.LEFT; 262 boolean mUseIntrinsicSizeAsMin = false; 263 int mInitialLevel = 0; 264 ScaleState(ScaleState orig, Resources res)265 ScaleState(ScaleState orig, Resources res) { 266 super(orig, res); 267 268 if (orig != null) { 269 mScaleWidth = orig.mScaleWidth; 270 mScaleHeight = orig.mScaleHeight; 271 mGravity = orig.mGravity; 272 mUseIntrinsicSizeAsMin = orig.mUseIntrinsicSizeAsMin; 273 mInitialLevel = orig.mInitialLevel; 274 } 275 } 276 277 @Override newDrawable(Resources res)278 public Drawable newDrawable(Resources res) { 279 return new ScaleDrawable(this, res); 280 } 281 } 282 283 /** 284 * Creates a new ScaleDrawable based on the specified constant state. 285 * <p> 286 * The resulting drawable is guaranteed to have a new constant state. 287 * 288 * @param state constant state from which the drawable inherits 289 */ ScaleDrawable(ScaleState state, Resources res)290 private ScaleDrawable(ScaleState state, Resources res) { 291 super(state, res); 292 293 mState = state; 294 295 updateLocalState(); 296 } 297 updateLocalState()298 private void updateLocalState() { 299 setLevel(mState.mInitialLevel); 300 } 301 } 302 303