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