package com.kaichunlin.transition.internal; import android.support.annotation.CheckResult; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.View; import com.kaichunlin.transition.TransitionConfig; import com.nineoldandroids.animation.Animator; import com.nineoldandroids.animation.AnimatorSet; import com.nineoldandroids.animation.ValueAnimator; import java.util.ArrayList; /** * NineOldAndroids' ObjectAnimator is used to provide required transition behavior. * <p> * TODO may be possible to switch to use NineOldDroid's ViewPropertyAnimator for better performance * <p> */ public class DefaultTransitionController extends TransitionController<DefaultTransitionController> implements Cloneable { protected AnimatorSet mAnimSet; private int mUpdateCount; /** * Wraps an Animator as a DefaultTransitionController * * @param anim * @return */ public static DefaultTransitionController wrapAnimator(@NonNull Animator anim) { AnimatorSet set = new AnimatorSet(); set.play(anim); return new DefaultTransitionController(set); } /** * Wraps an AnimatorSet as a DefaultTransitionController * * @param animSet * @return */ public static DefaultTransitionController wrapAnimatorSet(@NonNull AnimatorSet animSet) { return new DefaultTransitionController(animSet); } public DefaultTransitionController(@NonNull AnimatorSet mAnimSet) { this(null, mAnimSet); } /** * @param target the View that should be transitioned * @param mAnimSet */ public DefaultTransitionController(@Nullable View target, @NonNull AnimatorSet mAnimSet) { super(target); this.mAnimSet = mAnimSet; mStartDelay = mAnimSet.getStartDelay(); ArrayList<Animator> animators = mAnimSet.getChildAnimations(); final int size = animators.size(); Animator animator; for (int i = 0; i < size; i++) { animator = animators.get(i); if (!(animator instanceof ValueAnimator)) { throw new UnsupportedOperationException("Only ValueAnimator and its subclasses are supported: " + animator); } } mDuration = mAnimSet.getDuration(); if (mAnimSet.getDuration() >= 0) { long duration = mAnimSet.getDuration(); for (int i = 0; i < size; i++) { animators.get(i).setDuration(duration); } } else { for (int i = 0; i < size; i++) { animator = animators.get(i); long endTime = animator.getStartDelay() + animator.getDuration(); if (mDuration < endTime) { mDuration = endTime; } } } mTotalDuration = mStartDelay + mDuration; updateProgressWidth(); } @Override public void start() { super.start(); if (TransitionConfig.isDebug()) { getTransitionStateHolder().clear(); } if (mTarget == null && mInterpolator == null) { return; } ArrayList<Animator> animators = mAnimSet.getChildAnimations(); final int size = animators.size(); Animator animator; for (int i = 0; i < size; i++) { animator = animators.get(i); if (mTarget != null) { animator.setTarget(mTarget); } if (mInterpolator != null) { animator.setInterpolator(mInterpolator); } } } @Override public void updateProgress(float progress) { String debug = ""; final boolean DEBUG = TransitionConfig.isDebug(); long time = 0; if (mStart < mEnd && progress >= mStart && progress <= mEnd || mStart > mEnd && progress >= mEnd && progress <= mStart) { //forward progression if (mStart < mEnd) { time = (long) (mTotalDuration * (progress - mStart) / mProgressWidth); //backward } else { time = (long) (mTotalDuration - mTotalDuration * (progress - mEnd) / mProgressWidth); } time -= mStartDelay; if (time > 0) { mStarted = true; } if (DEBUG) { debug = "forward progression: [" + mStart + ".." + mEnd + "], mStarted=" + mStarted; } mUpdateCount++; } else { //forward if (mStart < mEnd) { if (progress < mStart) { time = 0; if (DEBUG) { debug = "forward progression: [" + mStart + ".." + mEnd + "], pre-start, progress=" + progress; } } else if (progress > mEnd) { time = mTotalDuration; if (mUpdateCount == 1) { mUpdateCount = -1; } if (DEBUG) { debug = "forward progression: [" + mStart + ".." + mEnd + "], post-finish, progress=" + progress; } } //backward } else if (mStart > mEnd) { if (progress > mStart) { time = 0; if (DEBUG) { debug = "forward progression: [" + mStart + ".." + mEnd + "], pre-start, progress=" + progress; } } else if (progress < mEnd) { time = mTotalDuration; if (mUpdateCount == 1) { mUpdateCount = -1; } if (DEBUG) { debug = "forward progression: [" + mStart + ".." + mEnd + "], post-finish, progress=" + progress; } } } } //TODO hack to make it work for ViewPager, removing mUpdateStateAfterUpdateProgress would break it for everything else // if (mSetup && mUpdateStateAfterUpdateProgress) { updateState(time); // } if (DEBUG) { appendLog("updateProgress: \t" + debug); } } private void updateState(long time) { if ((time == mLastTime || (!mStarted && !mSetup)) && mUpdateCount != -1) { return; } if (TransitionConfig.isDebug()) { appendLog("updateState: \t\ttime=" + time); } mSetup = false; mLastTime = time; ArrayList<Animator> animators = mAnimSet.getChildAnimations(); final int size = animators.size(); for (int i = 0; i < size; i++) { ValueAnimator va = (ValueAnimator) animators.get(i); long absTime = time - va.getStartDelay(); if (absTime >= 0) { va.setCurrentPlayTime(absTime); } } } private void appendLog(String msg) { getTransitionStateHolder().append(getId() + "->View" + mTarget.hashCode(), this, msg); } @CheckResult @Override public DefaultTransitionController clone() { return (DefaultTransitionController) super.clone(); } protected DefaultTransitionController self() { return this; } }