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.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.graphics.Canvas;
     22 import android.graphics.Rect;
     23 import android.graphics.ColorFilter;
     24 import android.graphics.PorterDuff.Mode;
     25 import android.content.res.ColorStateList;
     26 import android.content.res.Resources;
     27 import android.content.res.TypedArray;
     28 import android.content.res.Resources.Theme;
     29 import android.util.AttributeSet;
     30 import android.util.TypedValue;
     31 import android.util.Log;
     32 import android.os.SystemClock;
     33 
     34 import org.xmlpull.v1.XmlPullParser;
     35 import org.xmlpull.v1.XmlPullParserException;
     36 
     37 import java.io.IOException;
     38 
     39 import com.android.internal.R;
     40 
     41 /**
     42  * @hide
     43  */
     44 public class AnimatedRotateDrawable extends Drawable implements Drawable.Callback, Runnable,
     45         Animatable {
     46     private static final String TAG = "AnimatedRotateDrawable";
     47 
     48     private AnimatedRotateState mState;
     49     private boolean mMutated;
     50     private float mCurrentDegrees;
     51     private float mIncrement;
     52     private boolean mRunning;
     53 
     54     public AnimatedRotateDrawable() {
     55         this(null, null);
     56     }
     57 
     58     private AnimatedRotateDrawable(AnimatedRotateState rotateState, Resources res) {
     59         mState = new AnimatedRotateState(rotateState, this, res);
     60         init();
     61     }
     62 
     63     private void init() {
     64         final AnimatedRotateState state = mState;
     65         mIncrement = 360.0f / state.mFramesCount;
     66         final Drawable drawable = state.mDrawable;
     67         if (drawable != null) {
     68             drawable.setFilterBitmap(true);
     69             if (drawable instanceof BitmapDrawable) {
     70                 ((BitmapDrawable) drawable).setAntiAlias(true);
     71             }
     72         }
     73     }
     74 
     75     @Override
     76     public void draw(Canvas canvas) {
     77         int saveCount = canvas.save();
     78 
     79         final AnimatedRotateState st = mState;
     80         final Drawable drawable = st.mDrawable;
     81         final Rect bounds = drawable.getBounds();
     82 
     83         int w = bounds.right - bounds.left;
     84         int h = bounds.bottom - bounds.top;
     85 
     86         float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX;
     87         float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY;
     88 
     89         canvas.rotate(mCurrentDegrees, px + bounds.left, py + bounds.top);
     90 
     91         drawable.draw(canvas);
     92 
     93         canvas.restoreToCount(saveCount);
     94     }
     95 
     96     @Override
     97     public void start() {
     98         if (!mRunning) {
     99             mRunning = true;
    100             nextFrame();
    101         }
    102     }
    103 
    104     @Override
    105     public void stop() {
    106         mRunning = false;
    107         unscheduleSelf(this);
    108     }
    109 
    110     @Override
    111     public boolean isRunning() {
    112         return mRunning;
    113     }
    114 
    115     private void nextFrame() {
    116         unscheduleSelf(this);
    117         scheduleSelf(this, SystemClock.uptimeMillis() + mState.mFrameDuration);
    118     }
    119 
    120     @Override
    121     public void run() {
    122         // TODO: This should be computed in draw(Canvas), based on the amount
    123         // of time since the last frame drawn
    124         mCurrentDegrees += mIncrement;
    125         if (mCurrentDegrees > (360.0f - mIncrement)) {
    126             mCurrentDegrees = 0.0f;
    127         }
    128         invalidateSelf();
    129         nextFrame();
    130     }
    131 
    132     @Override
    133     public boolean setVisible(boolean visible, boolean restart) {
    134         mState.mDrawable.setVisible(visible, restart);
    135         boolean changed = super.setVisible(visible, restart);
    136         if (visible) {
    137             if (changed || restart) {
    138                 mCurrentDegrees = 0.0f;
    139                 nextFrame();
    140             }
    141         } else {
    142             unscheduleSelf(this);
    143         }
    144         return changed;
    145     }
    146 
    147     /**
    148      * Returns the drawable rotated by this RotateDrawable.
    149      */
    150     public Drawable getDrawable() {
    151         return mState.mDrawable;
    152     }
    153 
    154     @Override
    155     public int getChangingConfigurations() {
    156         return super.getChangingConfigurations()
    157                 | mState.mChangingConfigurations
    158                 | mState.mDrawable.getChangingConfigurations();
    159     }
    160 
    161     @Override
    162     public void setAlpha(int alpha) {
    163         mState.mDrawable.setAlpha(alpha);
    164     }
    165 
    166     @Override
    167     public int getAlpha() {
    168         return mState.mDrawable.getAlpha();
    169     }
    170 
    171     @Override
    172     public void setColorFilter(ColorFilter cf) {
    173         mState.mDrawable.setColorFilter(cf);
    174     }
    175 
    176     @Override
    177     public void setTintList(ColorStateList tint) {
    178         mState.mDrawable.setTintList(tint);
    179     }
    180 
    181     @Override
    182     public void setTintMode(Mode tintMode) {
    183         mState.mDrawable.setTintMode(tintMode);
    184     }
    185 
    186     @Override
    187     public int getOpacity() {
    188         return mState.mDrawable.getOpacity();
    189     }
    190 
    191     @Override
    192     public boolean canApplyTheme() {
    193         return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
    194     }
    195 
    196     @Override
    197     public void invalidateDrawable(Drawable who) {
    198         final Callback callback = getCallback();
    199         if (callback != null) {
    200             callback.invalidateDrawable(this);
    201         }
    202     }
    203 
    204     @Override
    205     public void scheduleDrawable(Drawable who, Runnable what, long when) {
    206         final Callback callback = getCallback();
    207         if (callback != null) {
    208             callback.scheduleDrawable(this, what, when);
    209         }
    210     }
    211 
    212     @Override
    213     public void unscheduleDrawable(Drawable who, Runnable what) {
    214         final Callback callback = getCallback();
    215         if (callback != null) {
    216             callback.unscheduleDrawable(this, what);
    217         }
    218     }
    219 
    220     @Override
    221     public boolean getPadding(Rect padding) {
    222         return mState.mDrawable.getPadding(padding);
    223     }
    224 
    225     @Override
    226     public boolean isStateful() {
    227         return mState.mDrawable.isStateful();
    228     }
    229 
    230     @Override
    231     protected void onBoundsChange(Rect bounds) {
    232         mState.mDrawable.setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom);
    233     }
    234 
    235     @Override
    236     protected boolean onLevelChange(int level) {
    237         return mState.mDrawable.setLevel(level);
    238     }
    239 
    240     @Override
    241     protected boolean onStateChange(int[] state) {
    242         return mState.mDrawable.setState(state);
    243     }
    244 
    245     @Override
    246     public int getIntrinsicWidth() {
    247         return mState.mDrawable.getIntrinsicWidth();
    248     }
    249 
    250     @Override
    251     public int getIntrinsicHeight() {
    252         return mState.mDrawable.getIntrinsicHeight();
    253     }
    254 
    255     @Override
    256     public ConstantState getConstantState() {
    257         if (mState.canConstantState()) {
    258             mState.mChangingConfigurations = getChangingConfigurations();
    259             return mState;
    260         }
    261         return null;
    262     }
    263 
    264     @Override
    265     public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
    266             @NonNull AttributeSet attrs, @Nullable Theme theme)
    267             throws XmlPullParserException, IOException {
    268         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedRotateDrawable);
    269         super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible);
    270         updateStateFromTypedArray(a);
    271         a.recycle();
    272 
    273         inflateChildElements(r, parser, attrs, theme);
    274 
    275         init();
    276     }
    277 
    278     @Override
    279     public void applyTheme(@Nullable Theme t) {
    280         super.applyTheme(t);
    281 
    282         final AnimatedRotateState state = mState;
    283         if (state == null) {
    284             return;
    285         }
    286 
    287         if (state.mThemeAttrs != null) {
    288             final TypedArray a = t.resolveAttributes(
    289                     state.mThemeAttrs, R.styleable.AnimatedRotateDrawable);
    290             try {
    291                 updateStateFromTypedArray(a);
    292                 verifyRequiredAttributes(a);
    293             } catch (XmlPullParserException e) {
    294                 throw new RuntimeException(e);
    295             } finally {
    296                 a.recycle();
    297             }
    298         }
    299 
    300         if (state.mDrawable != null && state.mDrawable.canApplyTheme()) {
    301             state.mDrawable.applyTheme(t);
    302         }
    303 
    304         init();
    305     }
    306 
    307     private void updateStateFromTypedArray(TypedArray a) {
    308         final AnimatedRotateState state = mState;
    309 
    310         // Account for any configuration changes.
    311         state.mChangingConfigurations |= a.getChangingConfigurations();
    312 
    313         // Extract the theme attributes, if any.
    314         state.mThemeAttrs = a.extractThemeAttrs();
    315 
    316         if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) {
    317             final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX);
    318             state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION;
    319             state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
    320         }
    321 
    322         if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotY)) {
    323             final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY);
    324             state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION;
    325             state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
    326         }
    327 
    328         setFramesCount(a.getInt(
    329                 R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount));
    330         setFramesDuration(a.getInt(
    331                 R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration));
    332 
    333         final Drawable dr = a.getDrawable(R.styleable.AnimatedRotateDrawable_drawable);
    334         if (dr != null) {
    335             state.mDrawable = dr;
    336             dr.setCallback(this);
    337         }
    338     }
    339 
    340     private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
    341             Theme theme) throws XmlPullParserException, IOException {
    342         final AnimatedRotateState state = mState;
    343 
    344         Drawable dr = null;
    345         int outerDepth = parser.getDepth();
    346         int type;
    347         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    348                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    349             if (type != XmlPullParser.START_TAG) {
    350                 continue;
    351             }
    352 
    353             if ((dr = Drawable.createFromXmlInner(r, parser, attrs, theme)) == null) {
    354                 Log.w(TAG, "Bad element under <animated-rotate>: " + parser.getName());
    355             }
    356         }
    357 
    358         if (dr != null) {
    359             state.mDrawable = dr;
    360             dr.setCallback(this);
    361         }
    362     }
    363 
    364     private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
    365         // If we're not waiting on a theme, verify required attributes.
    366         if (mState.mDrawable == null && (mState.mThemeAttrs == null
    367                 || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) {
    368             throw new XmlPullParserException(a.getPositionDescription()
    369                     + ": <animated-rotate> tag requires a 'drawable' attribute or "
    370                     + "child tag defining a drawable");
    371         }
    372     }
    373 
    374     public void setFramesCount(int framesCount) {
    375         mState.mFramesCount = framesCount;
    376         mIncrement = 360.0f / mState.mFramesCount;
    377     }
    378 
    379     public void setFramesDuration(int framesDuration) {
    380         mState.mFrameDuration = framesDuration;
    381     }
    382 
    383     @Override
    384     public Drawable mutate() {
    385         if (!mMutated && super.mutate() == this) {
    386             mState.mDrawable.mutate();
    387             mMutated = true;
    388         }
    389         return this;
    390     }
    391 
    392     /**
    393      * @hide
    394      */
    395     public void clearMutated() {
    396         super.clearMutated();
    397         mState.mDrawable.clearMutated();
    398         mMutated = false;
    399     }
    400 
    401     final static class AnimatedRotateState extends Drawable.ConstantState {
    402         Drawable mDrawable;
    403         int[] mThemeAttrs;
    404 
    405         int mChangingConfigurations;
    406 
    407         boolean mPivotXRel = false;
    408         float mPivotX = 0;
    409         boolean mPivotYRel = false;
    410         float mPivotY = 0;
    411         int mFrameDuration = 150;
    412         int mFramesCount = 12;
    413 
    414         private boolean mCanConstantState;
    415         private boolean mCheckedConstantState;
    416 
    417         public AnimatedRotateState(AnimatedRotateState orig, AnimatedRotateDrawable owner,
    418                 Resources res) {
    419             if (orig != null) {
    420                 if (res != null) {
    421                     mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
    422                 } else {
    423                     mDrawable = orig.mDrawable.getConstantState().newDrawable();
    424                 }
    425                 mDrawable.setCallback(owner);
    426                 mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection());
    427                 mDrawable.setBounds(orig.mDrawable.getBounds());
    428                 mDrawable.setLevel(orig.mDrawable.getLevel());
    429                 mThemeAttrs = orig.mThemeAttrs;
    430                 mPivotXRel = orig.mPivotXRel;
    431                 mPivotX = orig.mPivotX;
    432                 mPivotYRel = orig.mPivotYRel;
    433                 mPivotY = orig.mPivotY;
    434                 mFramesCount = orig.mFramesCount;
    435                 mFrameDuration = orig.mFrameDuration;
    436                 mCanConstantState = mCheckedConstantState = true;
    437             }
    438         }
    439 
    440         @Override
    441         public Drawable newDrawable() {
    442             return new AnimatedRotateDrawable(this, null);
    443         }
    444 
    445         @Override
    446         public Drawable newDrawable(Resources res) {
    447             return new AnimatedRotateDrawable(this, res);
    448         }
    449 
    450         @Override
    451         public boolean canApplyTheme() {
    452             return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme())
    453                     || super.canApplyTheme();
    454         }
    455 
    456         @Override
    457         public int getChangingConfigurations() {
    458             return mChangingConfigurations;
    459         }
    460 
    461         public boolean canConstantState() {
    462             if (!mCheckedConstantState) {
    463                 mCanConstantState = mDrawable.getConstantState() != null;
    464                 mCheckedConstantState = true;
    465             }
    466 
    467             return mCanConstantState;
    468         }
    469     }
    470 }
    471