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