/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.aviary.android.feather.graphics;
import java.io.IOException;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.util.AttributeSet;
import com.aviary.android.feather.Constants;
import com.aviary.android.feather.library.utils.ReflectionUtils;
import com.aviary.android.feather.library.utils.ReflectionUtils.ReflectionException;
/**
* Rotate Drawable.
*/
public class AnimatedRotateDrawable extends Drawable implements Drawable.Callback, Runnable, Animatable {
private AnimatedRotateState mState;
private boolean mMutated;
private float mCurrentDegrees;
private float mIncrement;
private boolean mRunning;
/**
* Instantiates a new animated rotate drawable.
*/
public AnimatedRotateDrawable() {
this( null, null );
}
/**
* Instantiates a new animated rotate drawable.
*
* @param res
* the res
* @param resId
* the res id
*/
public AnimatedRotateDrawable( Resources res, int resId ) {
this( res, resId, 8, 100 );
}
public AnimatedRotateDrawable( Resources res, int resId, int frames, int duration ) {
this( null, null );
final float pivotX = 0.5f;
final float pivotY = 0.5f;
final boolean pivotXRel = true;
final boolean pivotYRel = true;
setFramesCount( frames );
setFramesDuration( duration );
Drawable drawable = null;
if ( resId > 0 ) {
drawable = res.getDrawable( resId );
}
final AnimatedRotateState rotateState = mState;
rotateState.mDrawable = drawable;
rotateState.mPivotXRel = pivotXRel;
rotateState.mPivotX = pivotX;
rotateState.mPivotYRel = pivotYRel;
rotateState.mPivotY = pivotY;
init();
if ( drawable != null ) {
drawable.setCallback( this );
}
}
/**
* Instantiates a new animated rotate drawable.
*
* @param rotateState
* the rotate state
* @param res
* the res
*/
private AnimatedRotateDrawable( AnimatedRotateState rotateState, Resources res ) {
mState = new AnimatedRotateState( rotateState, this, res );
init();
}
/**
* Initialize
*/
private void init() {
final AnimatedRotateState state = mState;
mIncrement = 360.0f / state.mFramesCount;
final Drawable drawable = state.mDrawable;
if ( drawable != null ) {
drawable.setFilterBitmap( true );
if ( drawable instanceof BitmapDrawable ) {
( (BitmapDrawable) drawable ).setAntiAlias( true );
}
}
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#draw(android.graphics.Canvas)
*/
@Override
public void draw( Canvas canvas ) {
int saveCount = canvas.save();
final AnimatedRotateState st = mState;
final Drawable drawable = st.mDrawable;
final Rect bounds = drawable.getBounds();
int w = bounds.right - bounds.left;
int h = bounds.bottom - bounds.top;
float px = st.mPivotXRel ? ( w * st.mPivotX ) : st.mPivotX;
float py = st.mPivotYRel ? ( h * st.mPivotY ) : st.mPivotY;
canvas.rotate( mCurrentDegrees, px + bounds.left, py + bounds.top );
drawable.draw( canvas );
canvas.restoreToCount( saveCount );
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Animatable#start()
*/
@Override
public void start() {
if ( !mRunning ) {
mRunning = true;
nextFrame();
}
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Animatable#stop()
*/
@Override
public void stop() {
mRunning = false;
unscheduleSelf( this );
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Animatable#isRunning()
*/
@Override
public boolean isRunning() {
return mRunning;
}
/**
* Next frame.
*/
private void nextFrame() {
unscheduleSelf( this );
scheduleSelf( this, SystemClock.uptimeMillis() + mState.mFrameDuration );
}
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
mCurrentDegrees += mIncrement;
if ( mCurrentDegrees > ( 360.0f - mIncrement ) ) {
mCurrentDegrees = 0.0f;
}
invalidateSelf();
nextFrame();
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#setVisible(boolean, boolean)
*/
@Override
public boolean setVisible( boolean visible, boolean restart ) {
mState.mDrawable.setVisible( visible, restart );
boolean changed = super.setVisible( visible, restart );
if ( visible ) {
if ( changed || restart ) {
mCurrentDegrees = 0.0f;
nextFrame();
}
} else {
unscheduleSelf( this );
}
return changed;
}
/**
* Returns the drawable rotated by this RotateDrawable.
*
* @return the drawable
*/
public Drawable getDrawable() {
return mState.mDrawable;
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#getChangingConfigurations()
*/
@Override
public int getChangingConfigurations() {
return super.getChangingConfigurations() | mState.mChangingConfigurations | mState.mDrawable.getChangingConfigurations();
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#setAlpha(int)
*/
@Override
public void setAlpha( int alpha ) {
mState.mDrawable.setAlpha( alpha );
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#setColorFilter(android.graphics.ColorFilter)
*/
@Override
public void setColorFilter( ColorFilter cf ) {
mState.mDrawable.setColorFilter( cf );
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#getOpacity()
*/
@Override
public int getOpacity() {
return mState.mDrawable.getOpacity();
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable.Callback#invalidateDrawable(android.graphics.drawable.Drawable)
*/
@Override
public void invalidateDrawable( Drawable who ) {
if ( Constants.ANDROID_SDK > 10 ) {
Callback callback;
try {
callback = (Callback) ReflectionUtils.invokeMethod( this, "getCallback" );
} catch ( ReflectionException e ) {
return;
}
if ( callback != null ) {
callback.invalidateDrawable( this );
}
}
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable.Callback#scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable,
* long)
*/
@Override
public void scheduleDrawable( Drawable who, Runnable what, long when ) {
if ( Constants.ANDROID_SDK > 10 ) {
Callback callback;
try {
callback = (Callback) ReflectionUtils.invokeMethod( this, "getCallback" );
} catch ( ReflectionException e ) {
return;
}
if ( callback != null ) {
callback.scheduleDrawable( this, what, when );
}
}
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable.Callback#unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable)
*/
@Override
public void unscheduleDrawable( Drawable who, Runnable what ) {
if ( Constants.ANDROID_SDK > 10 ) {
Callback callback;
try {
callback = (Callback) ReflectionUtils.invokeMethod( this, "getCallback" );
} catch ( ReflectionException e ) {
return;
}
if ( callback != null ) {
callback.unscheduleDrawable( this, what );
}
}
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#getPadding(android.graphics.Rect)
*/
@Override
public boolean getPadding( Rect padding ) {
return mState.mDrawable.getPadding( padding );
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#isStateful()
*/
@Override
public boolean isStateful() {
return mState.mDrawable.isStateful();
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#onBoundsChange(android.graphics.Rect)
*/
@Override
protected void onBoundsChange( Rect bounds ) {
mState.mDrawable.setBounds( bounds.left, bounds.top, bounds.right, bounds.bottom );
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#getIntrinsicWidth()
*/
@Override
public int getIntrinsicWidth() {
return mState.mDrawable.getIntrinsicWidth();
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#getIntrinsicHeight()
*/
@Override
public int getIntrinsicHeight() {
return mState.mDrawable.getIntrinsicHeight();
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#getConstantState()
*/
@Override
public ConstantState getConstantState() {
if ( mState.canConstantState() ) {
mState.mChangingConfigurations = getChangingConfigurations();
return mState;
}
return null;
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser,
* android.util.AttributeSet)
*/
@Override
public void inflate( Resources r, XmlPullParser parser, AttributeSet attrs ) throws XmlPullParserException, IOException {}
/**
* Sets the frames count.
*
* @param framesCount
* the new frames count
*/
public void setFramesCount( int framesCount ) {
mState.mFramesCount = framesCount;
mIncrement = 360.0f / mState.mFramesCount;
}
/**
* Sets the frames duration.
*
* @param framesDuration
* the new frames duration
*/
public void setFramesDuration( int framesDuration ) {
mState.mFrameDuration = framesDuration;
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable#mutate()
*/
@Override
public Drawable mutate() {
if ( !mMutated && super.mutate() == this ) {
mState.mDrawable.mutate();
mMutated = true;
}
return this;
}
final static class AnimatedRotateState extends Drawable.ConstantState {
Drawable mDrawable;
int mChangingConfigurations;
boolean mPivotXRel;
float mPivotX;
boolean mPivotYRel;
float mPivotY;
int mFrameDuration;
int mFramesCount;
private boolean mCanConstantState;
private boolean mCheckedConstantState;
/**
* Instantiates a new animated rotate state.
*
* @param source
* the source
* @param owner
* the owner
* @param res
* the res
*/
public AnimatedRotateState( AnimatedRotateState source, AnimatedRotateDrawable owner, Resources res ) {
if ( source != null ) {
if ( res != null ) {
mDrawable = source.mDrawable.getConstantState().newDrawable( res );
} else {
mDrawable = source.mDrawable.getConstantState().newDrawable();
}
mDrawable.setCallback( owner );
mPivotXRel = source.mPivotXRel;
mPivotX = source.mPivotX;
mPivotYRel = source.mPivotYRel;
mPivotY = source.mPivotY;
mFramesCount = source.mFramesCount;
mFrameDuration = source.mFrameDuration;
mCanConstantState = mCheckedConstantState = true;
}
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable.ConstantState#newDrawable()
*/
@Override
public Drawable newDrawable() {
return new AnimatedRotateDrawable( this, null );
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable.ConstantState#newDrawable(android.content.res.Resources)
*/
@Override
public Drawable newDrawable( Resources res ) {
return new AnimatedRotateDrawable( this, res );
}
/*
* (non-Javadoc)
*
* @see android.graphics.drawable.Drawable.ConstantState#getChangingConfigurations()
*/
@Override
public int getChangingConfigurations() {
return mChangingConfigurations;
}
/**
* Can constant state.
*
* @return true, if successful
*/
public boolean canConstantState() {
if ( !mCheckedConstantState ) {
mCanConstantState = mDrawable.getConstantState() != null;
mCheckedConstantState = true;
}
return mCanConstantState;
}
}
}