| /* |
| * 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 org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import android.graphics.Canvas; |
| import android.graphics.ColorFilter; |
| import android.graphics.Rect; |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.util.TypedValue; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| |
| 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> |
| * |
| * <p>It can be defined in an XML file with the <code><rotate></code> element.</p> |
| * |
| * @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 Drawable implements Drawable.Callback { |
| private static final float MAX_LEVEL = 10000.0f; |
| |
| private RotateState mState; |
| private boolean mMutated; |
| |
| /** |
| * <p>Create a new rotating drawable with an empty state.</p> |
| */ |
| public RotateDrawable() { |
| this(null); |
| } |
| |
| /** |
| * <p>Create a new rotating drawable with the specified state. A copy of |
| * this state is used as the internal state for the newly created |
| * drawable.</p> |
| * |
| * @param rotateState the state for this drawable |
| */ |
| private RotateDrawable(RotateState rotateState) { |
| mState = new RotateState(rotateState, this); |
| } |
| |
| public void draw(Canvas canvas) { |
| int saveCount = canvas.save(); |
| |
| Rect bounds = mState.mDrawable.getBounds(); |
| |
| int w = bounds.right - bounds.left; |
| int h = bounds.bottom - bounds.top; |
| |
| final RotateState st = mState; |
| |
| float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX; |
| float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY; |
| |
| canvas.rotate(st.mCurrentDegrees, px, py); |
| |
| st.mDrawable.draw(canvas); |
| |
| canvas.restoreToCount(saveCount); |
| } |
| |
| /** |
| * Returns the drawable rotated by this RotateDrawable. |
| */ |
| public Drawable getDrawable() { |
| return mState.mDrawable; |
| } |
| |
| @Override |
| public int getChangingConfigurations() { |
| return super.getChangingConfigurations() |
| | mState.mChangingConfigurations |
| | mState.mDrawable.getChangingConfigurations(); |
| } |
| |
| public void setAlpha(int alpha) { |
| mState.mDrawable.setAlpha(alpha); |
| } |
| |
| public void setColorFilter(ColorFilter cf) { |
| mState.mDrawable.setColorFilter(cf); |
| } |
| |
| public int getOpacity() { |
| return mState.mDrawable.getOpacity(); |
| } |
| |
| public void invalidateDrawable(Drawable who) { |
| if (mCallback != null) { |
| mCallback.invalidateDrawable(this); |
| } |
| } |
| |
| public void scheduleDrawable(Drawable who, Runnable what, long when) { |
| if (mCallback != null) { |
| mCallback.scheduleDrawable(this, what, when); |
| } |
| } |
| |
| public void unscheduleDrawable(Drawable who, Runnable what) { |
| if (mCallback != null) { |
| mCallback.unscheduleDrawable(this, what); |
| } |
| } |
| |
| @Override |
| public boolean getPadding(Rect padding) { |
| return mState.mDrawable.getPadding(padding); |
| } |
| |
| @Override |
| public boolean setVisible(boolean visible, boolean restart) { |
| mState.mDrawable.setVisible(visible, restart); |
| return super.setVisible(visible, restart); |
| } |
| |
| @Override |
| public boolean isStateful() { |
| return mState.mDrawable.isStateful(); |
| } |
| |
| @Override |
| protected boolean onStateChange(int[] state) { |
| boolean changed = mState.mDrawable.setState(state); |
| onBoundsChange(getBounds()); |
| return changed; |
| } |
| |
| @Override |
| protected boolean onLevelChange(int level) { |
| mState.mDrawable.setLevel(level); |
| onBoundsChange(getBounds()); |
| |
| mState.mCurrentDegrees = mState.mFromDegrees + |
| (mState.mToDegrees - mState.mFromDegrees) * |
| ((float) level / MAX_LEVEL); |
| |
| invalidateSelf(); |
| return true; |
| } |
| |
| @Override |
| protected void onBoundsChange(Rect bounds) { |
| mState.mDrawable.setBounds(bounds.left, bounds.top, |
| bounds.right, bounds.bottom); |
| } |
| |
| @Override |
| public int getIntrinsicWidth() { |
| return mState.mDrawable.getIntrinsicWidth(); |
| } |
| |
| @Override |
| public int getIntrinsicHeight() { |
| return mState.mDrawable.getIntrinsicHeight(); |
| } |
| |
| @Override |
| public ConstantState getConstantState() { |
| if (mState.canConstantState()) { |
| mState.mChangingConfigurations = super.getChangingConfigurations(); |
| return mState; |
| } |
| return null; |
| } |
| |
| @Override |
| public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) |
| throws XmlPullParserException, IOException { |
| |
| TypedArray a = r.obtainAttributes(attrs, |
| com.android.internal.R.styleable.RotateDrawable); |
| |
| super.inflateWithAttributes(r, parser, a, |
| com.android.internal.R.styleable.RotateDrawable_visible); |
| |
| TypedValue tv = a.peekValue(com.android.internal.R.styleable.RotateDrawable_pivotX); |
| boolean pivotXRel = tv.type == TypedValue.TYPE_FRACTION; |
| float pivotX = pivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); |
| |
| tv = a.peekValue(com.android.internal.R.styleable.RotateDrawable_pivotY); |
| boolean pivotYRel = tv.type == TypedValue.TYPE_FRACTION; |
| float pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); |
| |
| float fromDegrees = a.getFloat( |
| com.android.internal.R.styleable.RotateDrawable_fromDegrees, 0.0f); |
| float toDegrees = a.getFloat( |
| com.android.internal.R.styleable.RotateDrawable_toDegrees, 360.0f); |
| |
| toDegrees = Math.max(fromDegrees, toDegrees); |
| |
| int res = a.getResourceId( |
| com.android.internal.R.styleable.RotateDrawable_drawable, 0); |
| Drawable drawable = null; |
| if (res > 0) { |
| drawable = r.getDrawable(res); |
| } |
| |
| a.recycle(); |
| |
| int outerDepth = parser.getDepth(); |
| int type; |
| while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && |
| (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { |
| |
| if (type != XmlPullParser.START_TAG) { |
| continue; |
| } |
| |
| if ((drawable = Drawable.createFromXmlInner(r, parser, attrs)) == null) { |
| Log.w("drawable", "Bad element under <rotate>: " |
| + parser .getName()); |
| } |
| } |
| |
| if (drawable == null) { |
| Log.w("drawable", "No drawable specified for <rotate>"); |
| } |
| |
| mState.mDrawable = drawable; |
| mState.mPivotXRel = pivotXRel; |
| mState.mPivotX = pivotX; |
| mState.mPivotYRel = pivotYRel; |
| mState.mPivotY = pivotY; |
| mState.mFromDegrees = mState.mCurrentDegrees = fromDegrees; |
| mState.mToDegrees = toDegrees; |
| |
| if (drawable != null) { |
| drawable.setCallback(this); |
| } |
| } |
| |
| @Override |
| public Drawable mutate() { |
| if (!mMutated && super.mutate() == this) { |
| mState.mDrawable.mutate(); |
| mMutated = true; |
| } |
| return this; |
| } |
| |
| /** |
| * <p>Represents the state of a rotation for a given drawable. The same |
| * rotate drawable can be invoked with different states to drive several |
| * rotations at the same time.</p> |
| */ |
| final static class RotateState extends Drawable.ConstantState { |
| Drawable mDrawable; |
| |
| int mChangingConfigurations; |
| |
| boolean mPivotXRel; |
| float mPivotX; |
| boolean mPivotYRel; |
| float mPivotY; |
| |
| float mFromDegrees; |
| float mToDegrees; |
| |
| float mCurrentDegrees; |
| |
| private boolean mCanConstantState; |
| private boolean mCheckedConstantState; |
| |
| public RotateState(RotateState source, RotateDrawable owner) { |
| if (source != null) { |
| mDrawable = source.mDrawable.getConstantState().newDrawable(); |
| mDrawable.setCallback(owner); |
| mPivotXRel = source.mPivotXRel; |
| mPivotX = source.mPivotX; |
| mPivotYRel = source.mPivotYRel; |
| mPivotY = source.mPivotY; |
| mFromDegrees = mCurrentDegrees = source.mFromDegrees; |
| mToDegrees = source.mToDegrees; |
| mCanConstantState = mCheckedConstantState = true; |
| } |
| } |
| |
| @Override |
| public Drawable newDrawable() { |
| return new RotateDrawable(this); |
| } |
| |
| @Override |
| public int getChangingConfigurations() { |
| return mChangingConfigurations; |
| } |
| |
| public boolean canConstantState() { |
| if (!mCheckedConstantState) { |
| mCanConstantState = mDrawable.getConstantState() != null; |
| mCheckedConstantState = true; |
| } |
| |
| return mCanConstantState; |
| } |
| } |
| } |