RotateDrawable.java revision b81b1447d2cfa9dc5bcc02c8a1b7193400a97ab6
1/* 2 * Copyright (C) 2007 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 org.xmlpull.v1.XmlPullParser; 20import org.xmlpull.v1.XmlPullParserException; 21 22import android.graphics.Canvas; 23import android.graphics.ColorFilter; 24import android.graphics.Rect; 25import android.content.res.Resources; 26import android.content.res.TypedArray; 27import android.util.TypedValue; 28import android.util.AttributeSet; 29import android.util.Log; 30 31import java.io.IOException; 32 33/** 34 * <p>A Drawable that can rotate another Drawable based on the current level 35 * value. The start and end angles of rotation can be controlled to map any 36 * circular arc to the level values range.</p> 37 * 38 * <p>It can be defined in an XML file with the <code><rotate></code> element.</p> 39 * 40 * @attr ref android.R.styleable#RotateDrawable_visible 41 * @attr ref android.R.styleable#RotateDrawable_fromDegrees 42 * @attr ref android.R.styleable#RotateDrawable_toDegrees 43 * @attr ref android.R.styleable#RotateDrawable_pivotX 44 * @attr ref android.R.styleable#RotateDrawable_pivotY 45 * @attr ref android.R.styleable#RotateDrawable_drawable 46 */ 47public class RotateDrawable extends Drawable implements Drawable.Callback { 48 private static final float MAX_LEVEL = 10000.0f; 49 50 private RotateState mState; 51 private boolean mMutated; 52 53 /** 54 * <p>Create a new rotating drawable with an empty state.</p> 55 */ 56 public RotateDrawable() { 57 this(null, null); 58 } 59 60 /** 61 * <p>Create a new rotating drawable with the specified state. A copy of 62 * this state is used as the internal state for the newly created 63 * drawable.</p> 64 * 65 * @param rotateState the state for this drawable 66 */ 67 private RotateDrawable(RotateState rotateState, Resources res) { 68 mState = new RotateState(rotateState, this, res); 69 } 70 71 public void draw(Canvas canvas) { 72 int saveCount = canvas.save(); 73 74 Rect bounds = mState.mDrawable.getBounds(); 75 76 int w = bounds.right - bounds.left; 77 int h = bounds.bottom - bounds.top; 78 79 final RotateState st = mState; 80 81 float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX; 82 float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY; 83 84 canvas.rotate(st.mCurrentDegrees, px, py); 85 86 st.mDrawable.draw(canvas); 87 88 canvas.restoreToCount(saveCount); 89 } 90 91 /** 92 * Returns the drawable rotated by this RotateDrawable. 93 */ 94 public Drawable getDrawable() { 95 return mState.mDrawable; 96 } 97 98 @Override 99 public int getChangingConfigurations() { 100 return super.getChangingConfigurations() 101 | mState.mChangingConfigurations 102 | mState.mDrawable.getChangingConfigurations(); 103 } 104 105 public void setAlpha(int alpha) { 106 mState.mDrawable.setAlpha(alpha); 107 } 108 109 public void setColorFilter(ColorFilter cf) { 110 mState.mDrawable.setColorFilter(cf); 111 } 112 113 public int getOpacity() { 114 return mState.mDrawable.getOpacity(); 115 } 116 117 public void invalidateDrawable(Drawable who) { 118 if (mCallback != null) { 119 mCallback.invalidateDrawable(this); 120 } 121 } 122 123 public void scheduleDrawable(Drawable who, Runnable what, long when) { 124 if (mCallback != null) { 125 mCallback.scheduleDrawable(this, what, when); 126 } 127 } 128 129 public void unscheduleDrawable(Drawable who, Runnable what) { 130 if (mCallback != null) { 131 mCallback.unscheduleDrawable(this, what); 132 } 133 } 134 135 @Override 136 public boolean getPadding(Rect padding) { 137 return mState.mDrawable.getPadding(padding); 138 } 139 140 @Override 141 public boolean setVisible(boolean visible, boolean restart) { 142 mState.mDrawable.setVisible(visible, restart); 143 return super.setVisible(visible, restart); 144 } 145 146 @Override 147 public boolean isStateful() { 148 return mState.mDrawable.isStateful(); 149 } 150 151 @Override 152 protected boolean onStateChange(int[] state) { 153 boolean changed = mState.mDrawable.setState(state); 154 onBoundsChange(getBounds()); 155 return changed; 156 } 157 158 @Override 159 protected boolean onLevelChange(int level) { 160 mState.mDrawable.setLevel(level); 161 onBoundsChange(getBounds()); 162 163 mState.mCurrentDegrees = mState.mFromDegrees + 164 (mState.mToDegrees - mState.mFromDegrees) * 165 ((float) level / MAX_LEVEL); 166 167 invalidateSelf(); 168 return true; 169 } 170 171 @Override 172 protected void onBoundsChange(Rect bounds) { 173 mState.mDrawable.setBounds(bounds.left, bounds.top, 174 bounds.right, bounds.bottom); 175 } 176 177 @Override 178 public int getIntrinsicWidth() { 179 return mState.mDrawable.getIntrinsicWidth(); 180 } 181 182 @Override 183 public int getIntrinsicHeight() { 184 return mState.mDrawable.getIntrinsicHeight(); 185 } 186 187 @Override 188 public ConstantState getConstantState() { 189 if (mState.canConstantState()) { 190 mState.mChangingConfigurations = super.getChangingConfigurations(); 191 return mState; 192 } 193 return null; 194 } 195 196 @Override 197 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 198 throws XmlPullParserException, IOException { 199 200 TypedArray a = r.obtainAttributes(attrs, 201 com.android.internal.R.styleable.RotateDrawable); 202 203 super.inflateWithAttributes(r, parser, a, 204 com.android.internal.R.styleable.RotateDrawable_visible); 205 206 TypedValue tv = a.peekValue(com.android.internal.R.styleable.RotateDrawable_pivotX); 207 boolean pivotXRel; 208 float pivotX; 209 if (tv == null) { 210 pivotXRel = true; 211 pivotX = 0.5f; 212 } else { 213 pivotXRel = tv.type == TypedValue.TYPE_FRACTION; 214 pivotX = pivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 215 } 216 217 tv = a.peekValue(com.android.internal.R.styleable.RotateDrawable_pivotY); 218 boolean pivotYRel; 219 float pivotY; 220 if (tv == null) { 221 pivotYRel = true; 222 pivotY = 0.5f; 223 } else { 224 pivotYRel = tv.type == TypedValue.TYPE_FRACTION; 225 pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 226 } 227 228 float fromDegrees = a.getFloat( 229 com.android.internal.R.styleable.RotateDrawable_fromDegrees, 0.0f); 230 float toDegrees = a.getFloat( 231 com.android.internal.R.styleable.RotateDrawable_toDegrees, 360.0f); 232 233 toDegrees = Math.max(fromDegrees, toDegrees); 234 235 int res = a.getResourceId( 236 com.android.internal.R.styleable.RotateDrawable_drawable, 0); 237 Drawable drawable = null; 238 if (res > 0) { 239 drawable = r.getDrawable(res); 240 } 241 242 a.recycle(); 243 244 int outerDepth = parser.getDepth(); 245 int type; 246 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && 247 (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 248 249 if (type != XmlPullParser.START_TAG) { 250 continue; 251 } 252 253 if ((drawable = Drawable.createFromXmlInner(r, parser, attrs)) == null) { 254 Log.w("drawable", "Bad element under <rotate>: " 255 + parser .getName()); 256 } 257 } 258 259 if (drawable == null) { 260 Log.w("drawable", "No drawable specified for <rotate>"); 261 } 262 263 mState.mDrawable = drawable; 264 mState.mPivotXRel = pivotXRel; 265 mState.mPivotX = pivotX; 266 mState.mPivotYRel = pivotYRel; 267 mState.mPivotY = pivotY; 268 mState.mFromDegrees = mState.mCurrentDegrees = fromDegrees; 269 mState.mToDegrees = toDegrees; 270 271 if (drawable != null) { 272 drawable.setCallback(this); 273 } 274 } 275 276 @Override 277 public Drawable mutate() { 278 if (!mMutated && super.mutate() == this) { 279 mState.mDrawable.mutate(); 280 mMutated = true; 281 } 282 return this; 283 } 284 285 /** 286 * <p>Represents the state of a rotation for a given drawable. The same 287 * rotate drawable can be invoked with different states to drive several 288 * rotations at the same time.</p> 289 */ 290 final static class RotateState extends Drawable.ConstantState { 291 Drawable mDrawable; 292 293 int mChangingConfigurations; 294 295 boolean mPivotXRel; 296 float mPivotX; 297 boolean mPivotYRel; 298 float mPivotY; 299 300 float mFromDegrees; 301 float mToDegrees; 302 303 float mCurrentDegrees; 304 305 private boolean mCanConstantState; 306 private boolean mCheckedConstantState; 307 308 public RotateState(RotateState source, RotateDrawable owner, Resources res) { 309 if (source != null) { 310 if (res != null) { 311 mDrawable = source.mDrawable.getConstantState().newDrawable(res); 312 } else { 313 mDrawable = source.mDrawable.getConstantState().newDrawable(); 314 } 315 mDrawable.setCallback(owner); 316 mPivotXRel = source.mPivotXRel; 317 mPivotX = source.mPivotX; 318 mPivotYRel = source.mPivotYRel; 319 mPivotY = source.mPivotY; 320 mFromDegrees = mCurrentDegrees = source.mFromDegrees; 321 mToDegrees = source.mToDegrees; 322 mCanConstantState = mCheckedConstantState = true; 323 } 324 } 325 326 @Override 327 public Drawable newDrawable() { 328 return new RotateDrawable(this, null); 329 } 330 331 @Override 332 public Drawable newDrawable(Resources res) { 333 return new RotateDrawable(this, res); 334 } 335 336 @Override 337 public int getChangingConfigurations() { 338 return mChangingConfigurations; 339 } 340 341 public boolean canConstantState() { 342 if (!mCheckedConstantState) { 343 mCanConstantState = mDrawable.getConstantState() != null; 344 mCheckedConstantState = true; 345 } 346 347 return mCanConstantState; 348 } 349 } 350} 351