package it.sephiroth.android.library.easing; import android.os.Handler; import android.os.SystemClock; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * Helper class to manage the Easing process.<br /> * Usage:<br /> * <pre> * // Create the easing manager instance * EasingManager manager = new EasingManager( this ); * // start a Linear easing animation using the easeOut method, from 0.0 to 1.0 * // and the duration of 200 ms * manager.start( Linear.class, EaseType.EaseOut, 0.0, 1.0, 200 ); * </pre> * * @author alessandro */ public final class EasingManager { static final int FPS = 60; static final int FRAME_TIME = 1000 / FPS; static final Handler mHandler = new Handler(); Easing mEasing; Method mMethod; boolean mRunning; long mBase; int mDuration; double mStartValue; double mEndValue; double mValue; boolean mInverted; EasingCallback mEasingCallback; String mToken; Ticker mTicker; public EasingManager(EasingCallback callback) { mEasingCallback = callback; mToken = String.valueOf(System.currentTimeMillis()); } public void start(Class<? extends Easing> clazz, EaseType type, double fromValue, double endValue, int durationMillis) { start(clazz, type, fromValue, endValue, durationMillis, 0); } /** * Start the easing with a delay * * @param clazz the Easing class to be used for the interpolation * @param type the Easing Type * @param fromValue the start value of the easing * @param endValue the end value of the easing * @param durationMillis the duration in ms of the easing * @param delayMillis the delay */ public void start(Class<? extends Easing> clazz, EaseType type, double fromValue, double endValue, int durationMillis, long delayMillis) { if (!mRunning) { mEasing = createInstance(clazz); if (null == mEasing) { return; } mMethod = getEasingMethod(mEasing, type); if (mMethod == null) { return; } mInverted = fromValue > endValue; if (mInverted) { mStartValue = endValue; mEndValue = fromValue; } else { mStartValue = fromValue; mEndValue = endValue; } mValue = mStartValue; mDuration = durationMillis; mBase = SystemClock.uptimeMillis() + delayMillis; mRunning = true; mTicker = new Ticker(); long next = SystemClock.uptimeMillis() + FRAME_TIME + delayMillis; if (delayMillis == 0) { mEasingCallback.onEasingStarted(fromValue); } else { mHandler.postAtTime(new TickerStart(fromValue), mToken, next - FRAME_TIME); } mHandler.postAtTime(mTicker, mToken, next); } } /** * Stop the current easing process. onEasingFinished will not be invoked */ public void stop() { mRunning = false; mHandler.removeCallbacks(mTicker, mToken); } Easing createInstance(Class<? extends Easing> clazz) { try { return clazz.newInstance(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } return null; } Method getEasingMethod(Easing instance, EaseType type) { String methodName = getMethodName(type); if (null != methodName) { Method m; try { m = instance.getClass().getMethod(methodName, double.class, double.class, double.class, double.class); } catch (SecurityException e) { e.printStackTrace(); return null; } catch (NoSuchMethodException e) { e.printStackTrace(); return null; } return m; } return null; } String getMethodName(EaseType type) { switch (type) { case EaseIn: return "easeIn"; case EaseInOut: return "easeInOut"; case EaseNone: return "easeNone"; case EaseOut: return "easeOut"; } return null; } /** * Note that easeNone is valid only used * with easing Linear */ public enum EaseType { EaseIn, EaseOut, EaseInOut, EaseNone } /** * Implement this callback in order to get updates from * the running easing */ public interface EasingCallback { void onEasingValueChanged(double value, double oldValue); void onEasingStarted(double value); void onEasingFinished(double value); } class TickerStart implements Runnable { double mValue; public TickerStart(double value) { mValue = value; } @Override public void run() { mEasingCallback.onEasingStarted(mValue); } } class Ticker implements Runnable { @Override public void run() { long base = mBase; long now = SystemClock.uptimeMillis(); long diff = now - base; double old = mValue; double value; try { value = (Double) mMethod.invoke(mEasing, diff, mStartValue, mEndValue, mDuration); } catch (IllegalArgumentException e) { e.printStackTrace(); return; } catch (IllegalAccessException e) { e.printStackTrace(); return; } catch (InvocationTargetException e) { e.printStackTrace(); return; } mValue = value; int frame = (int) (diff / FRAME_TIME); long next = base + ((frame + 1) * FRAME_TIME); if (diff < mDuration) { mEasingCallback.onEasingValueChanged(mInverted ? mEndValue - value : value, old); mHandler.postAtTime(this, mToken, next); } else { mEasingCallback.onEasingFinished(mInverted ? mEndValue : mStartValue); mRunning = false; } } } }