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