Home | History | Annotate | Download | only in drawable
      1 /*
      2  * Copyright (C) 2009 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 
     17 package android.graphics.drawable;
     18 
     19 import android.graphics.Canvas;
     20 import android.graphics.Rect;
     21 import android.graphics.ColorFilter;
     22 import android.content.res.Resources;
     23 import android.content.res.TypedArray;
     24 import android.util.AttributeSet;
     25 import android.util.TypedValue;
     26 import android.util.Log;
     27 import android.os.SystemClock;
     28 import org.xmlpull.v1.XmlPullParser;
     29 import org.xmlpull.v1.XmlPullParserException;
     30 
     31 import java.io.IOException;
     32 
     33 import com.android.internal.R;
     34 
     35 /**
     36  * @hide
     37  */
     38 public class AnimatedRotateDrawable extends Drawable implements Drawable.Callback, Runnable,
     39         Animatable {
     40 
     41     private AnimatedRotateState mState;
     42     private boolean mMutated;
     43     private float mCurrentDegrees;
     44     private float mIncrement;
     45     private boolean mRunning;
     46 
     47     public AnimatedRotateDrawable() {
     48         this(null, null);
     49     }
     50 
     51     private AnimatedRotateDrawable(AnimatedRotateState rotateState, Resources res) {
     52         mState = new AnimatedRotateState(rotateState, this, res);
     53         init();
     54     }
     55 
     56     private void init() {
     57         final AnimatedRotateState state = mState;
     58         mIncrement = 360.0f / state.mFramesCount;
     59         final Drawable drawable = state.mDrawable;
     60         if (drawable != null) {
     61             drawable.setFilterBitmap(true);
     62             if (drawable instanceof BitmapDrawable) {
     63                 ((BitmapDrawable) drawable).setAntiAlias(true);
     64             }
     65         }
     66     }
     67 
     68     @Override
     69     public void draw(Canvas canvas) {
     70         int saveCount = canvas.save();
     71 
     72         final AnimatedRotateState st = mState;
     73         final Drawable drawable = st.mDrawable;
     74         final Rect bounds = drawable.getBounds();
     75 
     76         int w = bounds.right - bounds.left;
     77         int h = bounds.bottom - bounds.top;
     78 
     79         float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX;
     80         float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY;
     81 
     82         canvas.rotate(mCurrentDegrees, px + bounds.left, py + bounds.top);
     83 
     84         drawable.draw(canvas);
     85 
     86         canvas.restoreToCount(saveCount);
     87     }
     88 
     89     public void start() {
     90         if (!mRunning) {
     91             mRunning = true;
     92             nextFrame();
     93         }
     94     }
     95 
     96     public void stop() {
     97         mRunning = false;
     98         unscheduleSelf(this);
     99     }
    100 
    101     public boolean isRunning() {
    102         return mRunning;
    103     }
    104 
    105     private void nextFrame() {
    106         unscheduleSelf(this);
    107         scheduleSelf(this, SystemClock.uptimeMillis() + mState.mFrameDuration);
    108     }
    109 
    110     public void run() {
    111         // TODO: This should be computed in draw(Canvas), based on the amount
    112         // of time since the last frame drawn
    113         mCurrentDegrees += mIncrement;
    114         if (mCurrentDegrees > (360.0f - mIncrement)) {
    115             mCurrentDegrees = 0.0f;
    116         }
    117         invalidateSelf();
    118         nextFrame();
    119     }
    120 
    121     @Override
    122     public boolean setVisible(boolean visible, boolean restart) {
    123         mState.mDrawable.setVisible(visible, restart);
    124         boolean changed = super.setVisible(visible, restart);
    125         if (visible) {
    126             if (changed || restart) {
    127                 mCurrentDegrees = 0.0f;
    128                 nextFrame();
    129             }
    130         } else {
    131             unscheduleSelf(this);
    132         }
    133         return changed;
    134     }
    135 
    136     /**
    137      * Returns the drawable rotated by this RotateDrawable.
    138      */
    139     public Drawable getDrawable() {
    140         return mState.mDrawable;
    141     }
    142 
    143     @Override
    144     public int getChangingConfigurations() {
    145         return super.getChangingConfigurations()
    146                 | mState.mChangingConfigurations
    147                 | mState.mDrawable.getChangingConfigurations();
    148     }
    149 
    150     @Override
    151     public void setAlpha(int alpha) {
    152         mState.mDrawable.setAlpha(alpha);
    153     }
    154 
    155     @Override
    156     public void setColorFilter(ColorFilter cf) {
    157         mState.mDrawable.setColorFilter(cf);
    158     }
    159 
    160     @Override
    161     public int getOpacity() {
    162         return mState.mDrawable.getOpacity();
    163     }
    164 
    165     public void invalidateDrawable(Drawable who) {
    166         final Callback callback = getCallback();
    167         if (callback != null) {
    168             callback.invalidateDrawable(this);
    169         }
    170     }
    171 
    172     public void scheduleDrawable(Drawable who, Runnable what, long when) {
    173         final Callback callback = getCallback();
    174         if (callback != null) {
    175             callback.scheduleDrawable(this, what, when);
    176         }
    177     }
    178 
    179     public void unscheduleDrawable(Drawable who, Runnable what) {
    180         final Callback callback = getCallback();
    181         if (callback != null) {
    182             callback.unscheduleDrawable(this, what);
    183         }
    184     }
    185 
    186     @Override
    187     public boolean getPadding(Rect padding) {
    188         return mState.mDrawable.getPadding(padding);
    189     }
    190 
    191     @Override
    192     public boolean isStateful() {
    193         return mState.mDrawable.isStateful();
    194     }
    195 
    196     @Override
    197     protected void onBoundsChange(Rect bounds) {
    198         mState.mDrawable.setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom);
    199     }
    200 
    201     @Override
    202     public int getIntrinsicWidth() {
    203         return mState.mDrawable.getIntrinsicWidth();
    204     }
    205 
    206     @Override
    207     public int getIntrinsicHeight() {
    208         return mState.mDrawable.getIntrinsicHeight();
    209     }
    210 
    211     @Override
    212     public ConstantState getConstantState() {
    213         if (mState.canConstantState()) {
    214             mState.mChangingConfigurations = getChangingConfigurations();
    215             return mState;
    216         }
    217         return null;
    218     }
    219 
    220     @Override
    221     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
    222             throws XmlPullParserException, IOException {
    223 
    224         final TypedArray a = r.obtainAttributes(attrs, R.styleable.AnimatedRotateDrawable);
    225 
    226         super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible);
    227 
    228         TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX);
    229         final boolean pivotXRel = tv.type == TypedValue.TYPE_FRACTION;
    230         final float pivotX = pivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
    231 
    232         tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY);
    233         final boolean pivotYRel = tv.type == TypedValue.TYPE_FRACTION;
    234         final float pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
    235 
    236         setFramesCount(a.getInt(R.styleable.AnimatedRotateDrawable_framesCount, 12));
    237         setFramesDuration(a.getInt(R.styleable.AnimatedRotateDrawable_frameDuration, 150));
    238 
    239         final int res = a.getResourceId(R.styleable.AnimatedRotateDrawable_drawable, 0);
    240         Drawable drawable = null;
    241         if (res > 0) {
    242             drawable = r.getDrawable(res);
    243         }
    244 
    245         a.recycle();
    246 
    247         int outerDepth = parser.getDepth();
    248         int type;
    249         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT &&
    250                (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    251 
    252             if (type != XmlPullParser.START_TAG) {
    253                 continue;
    254             }
    255 
    256             if ((drawable = Drawable.createFromXmlInner(r, parser, attrs)) == null) {
    257                 Log.w("drawable", "Bad element under <animated-rotate>: "
    258                         + parser .getName());
    259             }
    260         }
    261 
    262         if (drawable == null) {
    263             Log.w("drawable", "No drawable specified for <animated-rotate>");
    264         }
    265 
    266         final AnimatedRotateState rotateState = mState;
    267         rotateState.mDrawable = drawable;
    268         rotateState.mPivotXRel = pivotXRel;
    269         rotateState.mPivotX = pivotX;
    270         rotateState.mPivotYRel = pivotYRel;
    271         rotateState.mPivotY = pivotY;
    272 
    273         init();
    274 
    275         if (drawable != null) {
    276             drawable.setCallback(this);
    277         }
    278     }
    279 
    280     public void setFramesCount(int framesCount) {
    281         mState.mFramesCount = framesCount;
    282         mIncrement = 360.0f / mState.mFramesCount;
    283     }
    284 
    285     public void setFramesDuration(int framesDuration) {
    286         mState.mFrameDuration = framesDuration;
    287     }
    288 
    289     @Override
    290     public Drawable mutate() {
    291         if (!mMutated && super.mutate() == this) {
    292             mState.mDrawable.mutate();
    293             mMutated = true;
    294         }
    295         return this;
    296     }
    297 
    298     final static class AnimatedRotateState extends Drawable.ConstantState {
    299         Drawable mDrawable;
    300 
    301         int mChangingConfigurations;
    302 
    303         boolean mPivotXRel;
    304         float mPivotX;
    305         boolean mPivotYRel;
    306         float mPivotY;
    307         int mFrameDuration;
    308         int mFramesCount;
    309 
    310         private boolean mCanConstantState;
    311         private boolean mCheckedConstantState;
    312 
    313         public AnimatedRotateState(AnimatedRotateState source, AnimatedRotateDrawable owner,
    314                 Resources res) {
    315             if (source != null) {
    316                 if (res != null) {
    317                     mDrawable = source.mDrawable.getConstantState().newDrawable(res);
    318                 } else {
    319                     mDrawable = source.mDrawable.getConstantState().newDrawable();
    320                 }
    321                 mDrawable.setCallback(owner);
    322                 mPivotXRel = source.mPivotXRel;
    323                 mPivotX = source.mPivotX;
    324                 mPivotYRel = source.mPivotYRel;
    325                 mPivotY = source.mPivotY;
    326                 mFramesCount = source.mFramesCount;
    327                 mFrameDuration = source.mFrameDuration;
    328                 mCanConstantState = mCheckedConstantState = true;
    329             }
    330         }
    331 
    332         @Override
    333         public Drawable newDrawable() {
    334             return new AnimatedRotateDrawable(this, null);
    335         }
    336 
    337         @Override
    338         public Drawable newDrawable(Resources res) {
    339             return new AnimatedRotateDrawable(this, res);
    340         }
    341 
    342         @Override
    343         public int getChangingConfigurations() {
    344             return mChangingConfigurations;
    345         }
    346 
    347         public boolean canConstantState() {
    348             if (!mCheckedConstantState) {
    349                 mCanConstantState = mDrawable.getConstantState() != null;
    350                 mCheckedConstantState = true;
    351             }
    352 
    353             return mCanConstantState;
    354         }
    355     }
    356 }
    357