2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android
.graphics
.drawable
;
19 import com
.android
.internal
.R
;
21 import org
.xmlpull
.v1
.XmlPullParser
;
22 import org
.xmlpull
.v1
.XmlPullParserException
;
24 import android
.graphics
.Canvas
;
25 import android
.graphics
.Rect
;
26 import android
.content
.res
.Resources
;
27 import android
.content
.res
.TypedArray
;
28 import android
.content
.res
.Resources
.Theme
;
29 import android
.util
.MathUtils
;
30 import android
.util
.TypedValue
;
31 import android
.util
.AttributeSet
;
33 import java
.io
.IOException
;
37 * A Drawable that can rotate another Drawable based on the current level value.
38 * The start and end angles of rotation can be controlled to map any circular
39 * arc to the level values range.
41 * It can be defined in an XML file with the <code><rotate></code> element.
42 * For more information, see the guide to
43 * <a href="{@docRoot}guide/topics/resources/animation-resource.html">Animation Resources</a>.
45 * @attr ref android.R.styleable#RotateDrawable_visible
46 * @attr ref android.R.styleable#RotateDrawable_fromDegrees
47 * @attr ref android.R.styleable#RotateDrawable_toDegrees
48 * @attr ref android.R.styleable#RotateDrawable_pivotX
49 * @attr ref android.R.styleable#RotateDrawable_pivotY
50 * @attr ref android.R.styleable#RotateDrawable_drawable
52 public class RotateDrawable
extends DrawableWrapper
{
53 private static final int MAX_LEVEL
= 10000;
55 private RotateState mState
;
58 * Creates a new rotating drawable with no wrapped drawable.
60 public RotateDrawable() {
61 this(new RotateState(null), null);
65 public void inflate(Resources r
, XmlPullParser parser
, AttributeSet attrs
, Theme theme
)
66 throws XmlPullParserException
, IOException
{
67 final TypedArray a
= obtainAttributes(r
, theme
, attrs
, R
.styleable
.RotateDrawable
);
68 super.inflateWithAttributes(r
, parser
, a
, R
.styleable
.RotateDrawable_visible
);
70 updateStateFromTypedArray(a
);
71 inflateChildDrawable(r
, parser
, attrs
, theme
);
72 verifyRequiredAttributes(a
);
76 private void verifyRequiredAttributes(TypedArray a
) throws XmlPullParserException
{
77 // If we're not waiting on a theme, verify required attributes.
78 if (getDrawable() == null && (mState
.mThemeAttrs
== null
79 || mState
.mThemeAttrs
[R
.styleable
.RotateDrawable_drawable
] == 0)) {
80 throw new XmlPullParserException(a
.getPositionDescription()
81 + ": <rotate> tag requires a 'drawable' attribute or "
82 + "child tag defining a drawable");
87 void updateStateFromTypedArray(TypedArray a
) {
88 super.updateStateFromTypedArray(a
);
90 final RotateState state
= mState
;
92 // Extract the theme attributes, if any.
93 state
.mThemeAttrs
= a
.extractThemeAttrs();
95 if (a
.hasValue(R
.styleable
.RotateDrawable_pivotX
)) {
96 final TypedValue tv
= a
.peekValue(R
.styleable
.RotateDrawable_pivotX
);
97 state
.mPivotXRel
= tv
.type
== TypedValue
.TYPE_FRACTION
;
98 state
.mPivotX
= state
.mPivotXRel ? tv
.getFraction(1.0f
, 1.0f
) : tv
.getFloat();
101 if (a
.hasValue(R
.styleable
.RotateDrawable_pivotY
)) {
102 final TypedValue tv
= a
.peekValue(R
.styleable
.RotateDrawable_pivotY
);
103 state
.mPivotYRel
= tv
.type
== TypedValue
.TYPE_FRACTION
;
104 state
.mPivotY
= state
.mPivotYRel ? tv
.getFraction(1.0f
, 1.0f
) : tv
.getFloat();
107 state
.mFromDegrees
= a
.getFloat(
108 R
.styleable
.RotateDrawable_fromDegrees
, state
.mFromDegrees
);
109 state
.mToDegrees
= a
.getFloat(
110 R
.styleable
.RotateDrawable_toDegrees
, state
.mToDegrees
);
111 state
.mCurrentDegrees
= state
.mFromDegrees
;
113 final Drawable dr
= a
.getDrawable(R
.styleable
.RotateDrawable_drawable
);
120 public void applyTheme(Theme t
) {
121 final RotateState state
= mState
;
126 if (state
.mThemeAttrs
!= null) {
127 final TypedArray a
= t
.resolveAttributes(state
.mThemeAttrs
, R
.styleable
.RotateDrawable
);
129 updateStateFromTypedArray(a
);
130 verifyRequiredAttributes(a
);
131 } catch (XmlPullParserException e
) {
132 throw new RuntimeException(e
);
138 // The drawable may have changed as a result of applying the theme, so
139 // apply the theme to the wrapped drawable last.
144 public void draw(Canvas canvas
) {
145 final Drawable d
= getDrawable();
146 final Rect bounds
= d
.getBounds();
147 final int w
= bounds
.right
- bounds
.left
;
148 final int h
= bounds
.bottom
- bounds
.top
;
149 final RotateState st
= mState
;
150 final float px
= st
.mPivotXRel ?
(w
* st
.mPivotX
) : st
.mPivotX
;
151 final float py
= st
.mPivotYRel ?
(h
* st
.mPivotY
) : st
.mPivotY
;
153 final int saveCount
= canvas
.save();
154 canvas
.rotate(st
.mCurrentDegrees
, px
+ bounds
.left
, py
+ bounds
.top
);
156 canvas
.restoreToCount(saveCount
);
160 * Sets the start angle for rotation.
162 * @param fromDegrees starting angle in degrees
163 * @see #getFromDegrees()
164 * @attr ref android.R.styleable#RotateDrawable_fromDegrees
166 public void setFromDegrees(float fromDegrees
) {
167 if (mState
.mFromDegrees
!= fromDegrees
) {
168 mState
.mFromDegrees
= fromDegrees
;
174 * @return starting angle for rotation in degrees
175 * @see #setFromDegrees(float)
176 * @attr ref android.R.styleable#RotateDrawable_fromDegrees
178 public float getFromDegrees() {
179 return mState
.mFromDegrees
;
183 * Sets the end angle for rotation.
185 * @param toDegrees ending angle in degrees
186 * @see #getToDegrees()
187 * @attr ref android.R.styleable#RotateDrawable_toDegrees
189 public void setToDegrees(float toDegrees
) {
190 if (mState
.mToDegrees
!= toDegrees
) {
191 mState
.mToDegrees
= toDegrees
;
197 * @return ending angle for rotation in degrees
198 * @see #setToDegrees(float)
199 * @attr ref android.R.styleable#RotateDrawable_toDegrees
201 public float getToDegrees() {
202 return mState
.mToDegrees
;
206 * Sets the X position around which the drawable is rotated.
208 * @param pivotX X position around which to rotate. If the X pivot is
209 * relative, the position represents a fraction of the drawable
210 * width. Otherwise, the position represents an absolute value in
212 * @see #setPivotXRelative(boolean)
213 * @attr ref android.R.styleable#RotateDrawable_pivotX
215 public void setPivotX(float pivotX
) {
216 if (mState
.mPivotX
!= pivotX
) {
217 mState
.mPivotX
= pivotX
;
223 * @return X position around which to rotate
224 * @see #setPivotX(float)
225 * @attr ref android.R.styleable#RotateDrawable_pivotX
227 public float getPivotX() {
228 return mState
.mPivotX
;
232 * Sets whether the X pivot value represents a fraction of the drawable
233 * width or an absolute value in pixels.
235 * @param relative true if the X pivot represents a fraction of the drawable
236 * width, or false if it represents an absolute value in pixels
237 * @see #isPivotXRelative()
239 public void setPivotXRelative(boolean relative
) {
240 if (mState
.mPivotXRel
!= relative
) {
241 mState
.mPivotXRel
= relative
;
247 * @return true if the X pivot represents a fraction of the drawable width,
248 * or false if it represents an absolute value in pixels
249 * @see #setPivotXRelative(boolean)
251 public boolean isPivotXRelative() {
252 return mState
.mPivotXRel
;
256 * Sets the Y position around which the drawable is rotated.
258 * @param pivotY Y position around which to rotate. If the Y pivot is
259 * relative, the position represents a fraction of the drawable
260 * height. Otherwise, the position represents an absolute value
263 * @attr ref android.R.styleable#RotateDrawable_pivotY
265 public void setPivotY(float pivotY
) {
266 if (mState
.mPivotY
!= pivotY
) {
267 mState
.mPivotY
= pivotY
;
273 * @return Y position around which to rotate
274 * @see #setPivotY(float)
275 * @attr ref android.R.styleable#RotateDrawable_pivotY
277 public float getPivotY() {
278 return mState
.mPivotY
;
282 * Sets whether the Y pivot value represents a fraction of the drawable
283 * height or an absolute value in pixels.
285 * @param relative True if the Y pivot represents a fraction of the drawable
286 * height, or false if it represents an absolute value in pixels
287 * @see #isPivotYRelative()
289 public void setPivotYRelative(boolean relative
) {
290 if (mState
.mPivotYRel
!= relative
) {
291 mState
.mPivotYRel
= relative
;
297 * @return true if the Y pivot represents a fraction of the drawable height,
298 * or false if it represents an absolute value in pixels
299 * @see #setPivotYRelative(boolean)
301 public boolean isPivotYRelative() {
302 return mState
.mPivotYRel
;
306 protected boolean onLevelChange(int level
) {
307 super.onLevelChange(level
);
309 final float value
= level
/ (float) MAX_LEVEL
;
310 final float degrees
= MathUtils
.lerp(mState
.mFromDegrees
, mState
.mToDegrees
, value
);
311 mState
.mCurrentDegrees
= degrees
;
318 DrawableWrapperState
mutateConstantState() {
319 mState
= new RotateState(mState
);
323 static final class RotateState
extends DrawableWrapper
.DrawableWrapperState
{
324 boolean mPivotXRel
= true;
325 float mPivotX
= 0.5f
;
326 boolean mPivotYRel
= true;
327 float mPivotY
= 0.5f
;
328 float mFromDegrees
= 0.0f
;
329 float mToDegrees
= 360.0f
;
330 float mCurrentDegrees
= 0.0f
;
332 RotateState(RotateState orig
) {
336 mPivotXRel
= orig
.mPivotXRel
;
337 mPivotX
= orig
.mPivotX
;
338 mPivotYRel
= orig
.mPivotYRel
;
339 mPivotY
= orig
.mPivotY
;
340 mFromDegrees
= orig
.mFromDegrees
;
341 mToDegrees
= orig
.mToDegrees
;
342 mCurrentDegrees
= orig
.mCurrentDegrees
;
347 public Drawable
newDrawable(Resources res
) {
348 return new RotateDrawable(this, res
);
352 private RotateDrawable(RotateState state
, Resources res
) {