| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.graphics.drawable; |
| |
| import com.android.internal.R; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import android.graphics.Canvas; |
| import android.graphics.Rect; |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.content.res.Resources.Theme; |
| import android.util.MathUtils; |
| import android.util.TypedValue; |
| import android.util.AttributeSet; |
| |
| import java.io.IOException; |
| |
| /** |
| * <p> |
| * A Drawable that can rotate another Drawable based on the current level value. |
| * The start and end angles of rotation can be controlled to map any circular |
| * arc to the level values range. |
| * <p> |
| * It can be defined in an XML file with the <code><rotate></code> element. |
| * For more information, see the guide to |
| * <a href="{@docRoot}guide/topics/resources/animation-resource.html">Animation Resources</a>. |
| * |
| * @attr ref android.R.styleable#RotateDrawable_visible |
| * @attr ref android.R.styleable#RotateDrawable_fromDegrees |
| * @attr ref android.R.styleable#RotateDrawable_toDegrees |
| * @attr ref android.R.styleable#RotateDrawable_pivotX |
| * @attr ref android.R.styleable#RotateDrawable_pivotY |
| * @attr ref android.R.styleable#RotateDrawable_drawable |
| */ |
| public class RotateDrawable extends DrawableWrapper { |
| private static final int MAX_LEVEL = 10000; |
| |
| private RotateState mState; |
| |
| /** |
| * Creates a new rotating drawable with no wrapped drawable. |
| */ |
| public RotateDrawable() { |
| this(new RotateState(null), null); |
| } |
| |
| @Override |
| public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) |
| throws XmlPullParserException, IOException { |
| final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RotateDrawable); |
| super.inflateWithAttributes(r, parser, a, R.styleable.RotateDrawable_visible); |
| |
| updateStateFromTypedArray(a); |
| inflateChildDrawable(r, parser, attrs, theme); |
| verifyRequiredAttributes(a); |
| a.recycle(); |
| } |
| |
| private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { |
| // If we're not waiting on a theme, verify required attributes. |
| if (getDrawable() == null && (mState.mThemeAttrs == null |
| || mState.mThemeAttrs[R.styleable.RotateDrawable_drawable] == 0)) { |
| throw new XmlPullParserException(a.getPositionDescription() |
| + ": <rotate> tag requires a 'drawable' attribute or " |
| + "child tag defining a drawable"); |
| } |
| } |
| |
| @Override |
| void updateStateFromTypedArray(TypedArray a) { |
| super.updateStateFromTypedArray(a); |
| |
| final RotateState state = mState; |
| |
| // Extract the theme attributes, if any. |
| state.mThemeAttrs = a.extractThemeAttrs(); |
| |
| if (a.hasValue(R.styleable.RotateDrawable_pivotX)) { |
| final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotX); |
| state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION; |
| state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); |
| } |
| |
| if (a.hasValue(R.styleable.RotateDrawable_pivotY)) { |
| final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotY); |
| state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION; |
| state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); |
| } |
| |
| state.mFromDegrees = a.getFloat( |
| R.styleable.RotateDrawable_fromDegrees, state.mFromDegrees); |
| state.mToDegrees = a.getFloat( |
| R.styleable.RotateDrawable_toDegrees, state.mToDegrees); |
| state.mCurrentDegrees = state.mFromDegrees; |
| |
| final Drawable dr = a.getDrawable(R.styleable.RotateDrawable_drawable); |
| if (dr != null) { |
| setDrawable(dr); |
| } |
| } |
| |
| @Override |
| public void applyTheme(Theme t) { |
| final RotateState state = mState; |
| if (state == null) { |
| return; |
| } |
| |
| if (state.mThemeAttrs != null) { |
| final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable); |
| try { |
| updateStateFromTypedArray(a); |
| verifyRequiredAttributes(a); |
| } catch (XmlPullParserException e) { |
| throw new RuntimeException(e); |
| } finally { |
| a.recycle(); |
| } |
| } |
| |
| // The drawable may have changed as a result of applying the theme, so |
| // apply the theme to the wrapped drawable last. |
| super.applyTheme(t); |
| } |
| |
| @Override |
| public void draw(Canvas canvas) { |
| final Drawable d = getDrawable(); |
| final Rect bounds = d.getBounds(); |
| final int w = bounds.right - bounds.left; |
| final int h = bounds.bottom - bounds.top; |
| final RotateState st = mState; |
| final float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX; |
| final float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY; |
| |
| final int saveCount = canvas.save(); |
| canvas.rotate(st.mCurrentDegrees, px + bounds.left, py + bounds.top); |
| d.draw(canvas); |
| canvas.restoreToCount(saveCount); |
| } |
| |
| /** |
| * Sets the start angle for rotation. |
| * |
| * @param fromDegrees starting angle in degrees |
| * @see #getFromDegrees() |
| * @attr ref android.R.styleable#RotateDrawable_fromDegrees |
| */ |
| public void setFromDegrees(float fromDegrees) { |
| if (mState.mFromDegrees != fromDegrees) { |
| mState.mFromDegrees = fromDegrees; |
| invalidateSelf(); |
| } |
| } |
| |
| /** |
| * @return starting angle for rotation in degrees |
| * @see #setFromDegrees(float) |
| * @attr ref android.R.styleable#RotateDrawable_fromDegrees |
| */ |
| public float getFromDegrees() { |
| return mState.mFromDegrees; |
| } |
| |
| /** |
| * Sets the end angle for rotation. |
| * |
| * @param toDegrees ending angle in degrees |
| * @see #getToDegrees() |
| * @attr ref android.R.styleable#RotateDrawable_toDegrees |
| */ |
| public void setToDegrees(float toDegrees) { |
| if (mState.mToDegrees != toDegrees) { |
| mState.mToDegrees = toDegrees; |
| invalidateSelf(); |
| } |
| } |
| |
| /** |
| * @return ending angle for rotation in degrees |
| * @see #setToDegrees(float) |
| * @attr ref android.R.styleable#RotateDrawable_toDegrees |
| */ |
| public float getToDegrees() { |
| return mState.mToDegrees; |
| } |
| |
| /** |
| * Sets the X position around which the drawable is rotated. |
| * |
| * @param pivotX X position around which to rotate. If the X pivot is |
| * relative, the position represents a fraction of the drawable |
| * width. Otherwise, the position represents an absolute value in |
| * pixels. |
| * @see #setPivotXRelative(boolean) |
| * @attr ref android.R.styleable#RotateDrawable_pivotX |
| */ |
| public void setPivotX(float pivotX) { |
| if (mState.mPivotX != pivotX) { |
| mState.mPivotX = pivotX; |
| invalidateSelf(); |
| } |
| } |
| |
| /** |
| * @return X position around which to rotate |
| * @see #setPivotX(float) |
| * @attr ref android.R.styleable#RotateDrawable_pivotX |
| */ |
| public float getPivotX() { |
| return mState.mPivotX; |
| } |
| |
| /** |
| * Sets whether the X pivot value represents a fraction of the drawable |
| * width or an absolute value in pixels. |
| * |
| * @param relative true if the X pivot represents a fraction of the drawable |
| * width, or false if it represents an absolute value in pixels |
| * @see #isPivotXRelative() |
| */ |
| public void setPivotXRelative(boolean relative) { |
| if (mState.mPivotXRel != relative) { |
| mState.mPivotXRel = relative; |
| invalidateSelf(); |
| } |
| } |
| |
| /** |
| * @return true if the X pivot represents a fraction of the drawable width, |
| * or false if it represents an absolute value in pixels |
| * @see #setPivotXRelative(boolean) |
| */ |
| public boolean isPivotXRelative() { |
| return mState.mPivotXRel; |
| } |
| |
| /** |
| * Sets the Y position around which the drawable is rotated. |
| * |
| * @param pivotY Y position around which to rotate. If the Y pivot is |
| * relative, the position represents a fraction of the drawable |
| * height. Otherwise, the position represents an absolute value |
| * in pixels. |
| * @see #getPivotY() |
| * @attr ref android.R.styleable#RotateDrawable_pivotY |
| */ |
| public void setPivotY(float pivotY) { |
| if (mState.mPivotY != pivotY) { |
| mState.mPivotY = pivotY; |
| invalidateSelf(); |
| } |
| } |
| |
| /** |
| * @return Y position around which to rotate |
| * @see #setPivotY(float) |
| * @attr ref android.R.styleable#RotateDrawable_pivotY |
| */ |
| public float getPivotY() { |
| return mState.mPivotY; |
| } |
| |
| /** |
| * Sets whether the Y pivot value represents a fraction of the drawable |
| * height or an absolute value in pixels. |
| * |
| * @param relative True if the Y pivot represents a fraction of the drawable |
| * height, or false if it represents an absolute value in pixels |
| * @see #isPivotYRelative() |
| */ |
| public void setPivotYRelative(boolean relative) { |
| if (mState.mPivotYRel != relative) { |
| mState.mPivotYRel = relative; |
| invalidateSelf(); |
| } |
| } |
| |
| /** |
| * @return true if the Y pivot represents a fraction of the drawable height, |
| * or false if it represents an absolute value in pixels |
| * @see #setPivotYRelative(boolean) |
| */ |
| public boolean isPivotYRelative() { |
| return mState.mPivotYRel; |
| } |
| |
| @Override |
| protected boolean onLevelChange(int level) { |
| super.onLevelChange(level); |
| |
| final float value = level / (float) MAX_LEVEL; |
| final float degrees = MathUtils.lerp(mState.mFromDegrees, mState.mToDegrees, value); |
| mState.mCurrentDegrees = degrees; |
| |
| invalidateSelf(); |
| return true; |
| } |
| |
| @Override |
| DrawableWrapperState mutateConstantState() { |
| mState = new RotateState(mState); |
| return mState; |
| } |
| |
| static final class RotateState extends DrawableWrapper.DrawableWrapperState { |
| boolean mPivotXRel = true; |
| float mPivotX = 0.5f; |
| boolean mPivotYRel = true; |
| float mPivotY = 0.5f; |
| float mFromDegrees = 0.0f; |
| float mToDegrees = 360.0f; |
| float mCurrentDegrees = 0.0f; |
| |
| RotateState(RotateState orig) { |
| super(orig); |
| |
| if (orig != null) { |
| mPivotXRel = orig.mPivotXRel; |
| mPivotX = orig.mPivotX; |
| mPivotYRel = orig.mPivotYRel; |
| mPivotY = orig.mPivotY; |
| mFromDegrees = orig.mFromDegrees; |
| mToDegrees = orig.mToDegrees; |
| mCurrentDegrees = orig.mCurrentDegrees; |
| } |
| } |
| |
| @Override |
| public Drawable newDrawable(Resources res) { |
| return new RotateDrawable(this, res); |
| } |
| } |
| |
| private RotateDrawable(RotateState state, Resources res) { |
| super(state, res); |
| |
| mState = state; |
| } |
| } |