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