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 + bounds.left, py + bounds.top);
     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         final Callback callback = getCallback();
    121         if (callback != null) {
    122             callback.invalidateDrawable(this);
    123         }
    124     }
    125 
    126     public void scheduleDrawable(Drawable who, Runnable what, long when) {
    127         final Callback callback = getCallback();
    128         if (callback != null) {
    129             callback.scheduleDrawable(this, what, when);
    130         }
    131     }
    132 
    133     public void unscheduleDrawable(Drawable who, Runnable what) {
    134         final Callback callback = getCallback();
    135         if (callback != null) {
    136             callback.unscheduleDrawable(this, what);
    137         }
    138     }
    139 
    140    @Override
    141     public boolean getPadding(Rect padding) {
    142         return mState.mDrawable.getPadding(padding);
    143     }
    144 
    145     @Override
    146     public boolean setVisible(boolean visible, boolean restart) {
    147         mState.mDrawable.setVisible(visible, restart);
    148         return super.setVisible(visible, restart);
    149     }
    150 
    151     @Override
    152     public boolean isStateful() {
    153         return mState.mDrawable.isStateful();
    154     }
    155 
    156     @Override
    157     protected boolean onStateChange(int[] state) {
    158         boolean changed = mState.mDrawable.setState(state);
    159         onBoundsChange(getBounds());
    160         return changed;
    161     }
    162 
    163     @Override
    164     protected boolean onLevelChange(int level) {
    165         mState.mDrawable.setLevel(level);
    166         onBoundsChange(getBounds());
    167 
    168         mState.mCurrentDegrees = mState.mFromDegrees +
    169                 (mState.mToDegrees - mState.mFromDegrees) *
    170                         ((float) level / MAX_LEVEL);
    171 
    172         invalidateSelf();
    173         return true;
    174     }
    175 
    176     @Override
    177     protected void onBoundsChange(Rect bounds) {
    178         mState.mDrawable.setBounds(bounds.left, bounds.top,
    179                 bounds.right, bounds.bottom);
    180     }
    181 
    182     @Override
    183     public int getIntrinsicWidth() {
    184         return mState.mDrawable.getIntrinsicWidth();
    185     }
    186 
    187     @Override
    188     public int getIntrinsicHeight() {
    189         return mState.mDrawable.getIntrinsicHeight();
    190     }
    191 
    192     @Override
    193     public ConstantState getConstantState() {
    194         if (mState.canConstantState()) {
    195             mState.mChangingConfigurations = getChangingConfigurations();
    196             return mState;
    197         }
    198         return null;
    199     }
    200 
    201     @Override
    202     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
    203             throws XmlPullParserException, IOException {
    204 
    205         TypedArray a = r.obtainAttributes(attrs,
    206                 com.android.internal.R.styleable.RotateDrawable);
    207 
    208         super.inflateWithAttributes(r, parser, a,
    209                 com.android.internal.R.styleable.RotateDrawable_visible);
    210 
    211         TypedValue tv = a.peekValue(com.android.internal.R.styleable.RotateDrawable_pivotX);
    212         boolean pivotXRel;
    213         float pivotX;
    214         if (tv == null) {
    215             pivotXRel = true;
    216             pivotX = 0.5f;
    217         } else {
    218             pivotXRel = tv.type == TypedValue.TYPE_FRACTION;
    219             pivotX = pivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
    220         }
    221 
    222         tv = a.peekValue(com.android.internal.R.styleable.RotateDrawable_pivotY);
    223         boolean pivotYRel;
    224         float pivotY;
    225         if (tv == null) {
    226             pivotYRel = true;
    227             pivotY = 0.5f;
    228         } else {
    229             pivotYRel = tv.type == TypedValue.TYPE_FRACTION;
    230             pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
    231         }
    232 
    233         float fromDegrees = a.getFloat(
    234                 com.android.internal.R.styleable.RotateDrawable_fromDegrees, 0.0f);
    235         float toDegrees = a.getFloat(
    236                 com.android.internal.R.styleable.RotateDrawable_toDegrees, 360.0f);
    237 
    238         int res = a.getResourceId(
    239                 com.android.internal.R.styleable.RotateDrawable_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 <rotate>: "
    258                         + parser .getName());
    259             }
    260         }
    261 
    262         if (drawable == null) {
    263             Log.w("drawable", "No drawable specified for <rotate>");
    264         }
    265 
    266         mState.mDrawable = drawable;
    267         mState.mPivotXRel = pivotXRel;
    268         mState.mPivotX = pivotX;
    269         mState.mPivotYRel = pivotYRel;
    270         mState.mPivotY = pivotY;
    271         mState.mFromDegrees = mState.mCurrentDegrees = fromDegrees;
    272         mState.mToDegrees = toDegrees;
    273 
    274         if (drawable != null) {
    275             drawable.setCallback(this);
    276         }
    277     }
    278 
    279     @Override
    280     public Drawable mutate() {
    281         if (!mMutated && super.mutate() == this) {
    282             mState.mDrawable.mutate();
    283             mMutated = true;
    284         }
    285         return this;
    286     }
    287 
    288     /**
    289      * <p>Represents the state of a rotation for a given drawable. The same
    290      * rotate drawable can be invoked with different states to drive several
    291      * rotations at the same time.</p>
    292      */
    293     final static class RotateState extends Drawable.ConstantState {
    294         Drawable mDrawable;
    295 
    296         int mChangingConfigurations;
    297 
    298         boolean mPivotXRel;
    299         float mPivotX;
    300         boolean mPivotYRel;
    301         float mPivotY;
    302 
    303         float mFromDegrees;
    304         float mToDegrees;
    305 
    306         float mCurrentDegrees;
    307 
    308         private boolean mCanConstantState;
    309         private boolean mCheckedConstantState;
    310 
    311         public RotateState(RotateState source, RotateDrawable owner, Resources res) {
    312             if (source != null) {
    313                 if (res != null) {
    314                     mDrawable = source.mDrawable.getConstantState().newDrawable(res);
    315                 } else {
    316                     mDrawable = source.mDrawable.getConstantState().newDrawable();
    317                 }
    318                 mDrawable.setCallback(owner);
    319                 mPivotXRel = source.mPivotXRel;
    320                 mPivotX = source.mPivotX;
    321                 mPivotYRel = source.mPivotYRel;
    322                 mPivotY = source.mPivotY;
    323                 mFromDegrees = mCurrentDegrees = source.mFromDegrees;
    324                 mToDegrees = source.mToDegrees;
    325                 mCanConstantState = mCheckedConstantState = true;
    326             }
    327         }
    328 
    329         @Override
    330         public Drawable newDrawable() {
    331             return new RotateDrawable(this, null);
    332         }
    333 
    334         @Override
    335         public Drawable newDrawable(Resources res) {
    336             return new RotateDrawable(this, res);
    337         }
    338 
    339         @Override
    340         public int getChangingConfigurations() {
    341             return mChangingConfigurations;
    342         }
    343 
    344         public boolean canConstantState() {
    345             if (!mCheckedConstantState) {
    346                 mCanConstantState = mDrawable.getConstantState() != null;
    347                 mCheckedConstantState = true;
    348             }
    349 
    350             return mCanConstantState;
    351         }
    352     }
    353 }
    354