/******************************************************************************* * Copyright 2013 Comcast Cable Communications Management, LLC * * 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.marshalchen.common.uimodule.freeflow.animations; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.graphics.Rect; import android.util.Log; import android.util.Pair; import android.view.MotionEvent; import android.view.View; import android.view.View.MeasureSpec; import android.view.animation.DecelerateInterpolator; import com.marshalchen.common.commonUtils.logUtils.Logs; import com.marshalchen.common.uimodule.freeflow.core.FreeFlowContainer; import com.marshalchen.common.uimodule.freeflow.core.FreeFlowItem; import com.marshalchen.common.uimodule.freeflow.core.LayoutChangeset; public class DefaultLayoutAnimator implements FreeFlowLayoutAnimator { protected LayoutChangeset changeSet; public static final String TAG = "DefaultLayoutAnimator"; /** * The duration of the "Appear" animation per new cell being added. Note * that the total time of the "Appear" animation will be based on the * cumulative total of this animation playing on each cell */ public int newCellsAdditionAnimationDurationPerCell = 200; public int newCellsAdditionAnimationStartDelay = 0; /** * The duration of the "Disappearing" animation per old cell being removed. * Note that the total time of the "Disappearing" animation will be based on * the cumulative total of this animation playing on each cell being removed */ public int oldCellsRemovalAnimationDuration = 200; public int oldCellsRemovalAnimationStartDelay = 0; public int cellPositionTransitionAnimationDuration = 250; /** * If set to true, this forces the animation sets to animate in the * following sequence: delete then add then move * <p/> * If set to false, all sets will animate in parallel */ public boolean animateAllSetsSequentially = false; /** * If set to true, this forces each view in a set to animate sequentially * <p/> * If set to false, all views for a set will animate in parallel */ public boolean animateIndividualCellsSequentially = false; protected FreeFlowContainer callback; protected AnimatorSet disappearingSet = null; protected AnimatorSet appearingSet = null; protected AnimatorSet movingSet = null; public DefaultLayoutAnimator() { } @Override public void cancel() { if (disappearingSet != null) disappearingSet.cancel(); if (appearingSet != null) appearingSet.cancel(); if (movingSet != null) movingSet.cancel(); for (FreeFlowItem item : changeSet.getAdded()) { item.view.setAlpha(1f); } for (FreeFlowItem item : changeSet.getRemoved()) { item.view.setAlpha(1f); } mIsRunning = false; } protected boolean mIsRunning = false; @Override public void animateChanges(LayoutChangeset changeSet, final FreeFlowContainer callback) { this.changeSet = changeSet; this.callback = callback; cancel(); mIsRunning = true; disappearingSet = null; appearingSet = null; movingSet = null; Comparator<FreeFlowItem> cmp = new Comparator<FreeFlowItem>() { @Override public int compare(FreeFlowItem lhs, FreeFlowItem rhs) { return (lhs.itemSection * 1000 + lhs.itemIndex) - (rhs.itemSection * 1000 + rhs.itemIndex); } }; List<FreeFlowItem> removed = changeSet.getRemoved(); if (removed.size() > 0) { Collections.sort(removed, cmp); disappearingSet = getItemsRemovedAnimation(changeSet.getRemoved()); } List<FreeFlowItem> added = changeSet.getAdded(); if (added.size() > 0) { Collections.sort(added, cmp); appearingSet = getItemsAddedAnimation(added); } if (changeSet.getMoved().size() > 0) { movingSet = getItemsMovedAnimation(changeSet.getMoved()); } AnimatorSet all = getAnimationSequence(); if (all == null) { mIsRunning = false; callback.onLayoutChangeAnimationsCompleted(this); } else { all.addListener(new AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { mIsRunning = false; callback.onLayoutChangeAnimationsCompleted(DefaultLayoutAnimator.this); } @Override public void onAnimationCancel(Animator animation) { } }); all.start(); } } /** * The animation to run on the items being removed * * @param removed An ArrayList of <code>FreeFlowItems</code> removed * @return The AnimatorSet of the removed objects */ protected AnimatorSet getItemsRemovedAnimation(List<FreeFlowItem> removed) { AnimatorSet disappearingSet = new AnimatorSet(); ArrayList<Animator> fades = new ArrayList<Animator>(); for (FreeFlowItem proxy : removed) { fades.add(ObjectAnimator.ofFloat(proxy.view, "alpha", 0)); } disappearingSet.setDuration(oldCellsRemovalAnimationDuration); disappearingSet.setStartDelay(oldCellsRemovalAnimationStartDelay); if (animateIndividualCellsSequentially) disappearingSet.playSequentially(fades); else disappearingSet.playTogether(fades); return disappearingSet; } /** * */ protected AnimatorSet getItemsAddedAnimation(List<FreeFlowItem> added) { AnimatorSet appearingSet = new AnimatorSet(); ArrayList<Animator> fadeIns = new ArrayList<Animator>(); for (FreeFlowItem proxy : added) { proxy.view.setAlpha(0); fadeIns.add(ObjectAnimator.ofFloat(proxy.view, "alpha", 1)); } if (animateIndividualCellsSequentially) appearingSet.playSequentially(fadeIns); else appearingSet.playTogether(fadeIns); appearingSet.setStartDelay(newCellsAdditionAnimationStartDelay); appearingSet.setDuration(newCellsAdditionAnimationDurationPerCell); return appearingSet; } protected AnimatorSet getAnimationSequence() { if (disappearingSet == null && appearingSet == null && movingSet == null) return null; AnimatorSet allAnim = new AnimatorSet(); ArrayList<Animator> all = new ArrayList<Animator>(); if (disappearingSet != null) all.add(disappearingSet); if (appearingSet != null) all.add(appearingSet); if (movingSet != null) all.add(movingSet); if (animateAllSetsSequentially) allAnim.playSequentially(all); else allAnim.playTogether(all); return allAnim; } protected AnimatorSet getItemsMovedAnimation(List<Pair<FreeFlowItem, Rect>> moved) { AnimatorSet anim = new AnimatorSet(); ArrayList<Animator> moves = new ArrayList<Animator>(); for (Pair<FreeFlowItem, Rect> item : moved) { FreeFlowItem proxy = FreeFlowItem.clone(item.first); View v = proxy.view; proxy.frame.left -= callback.getViewportLeft(); proxy.frame.top -= callback.getViewportTop(); proxy.frame.right -= callback.getViewportLeft(); proxy.frame.bottom -= callback.getViewportTop(); moves.add(transitionToFrame(item.second, proxy, v)); } anim.playTogether(moves); return anim; } // @Override public ValueAnimator transitionToFrame(final Rect of, final FreeFlowItem nf, final View v) { ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); anim.setDuration(cellPositionTransitionAnimationDuration); anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { try { int itemWidth = of.width() + (int) ((nf.frame.width() - of.width()) * animation.getAnimatedFraction()); int itemHeight = of.height() + (int) ((nf.frame.height() - of.height()) * animation.getAnimatedFraction()); int widthSpec = MeasureSpec.makeMeasureSpec(itemWidth, MeasureSpec.EXACTLY); int heightSpec = MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY); v.measure(widthSpec, heightSpec); Rect frame = new Rect(); Rect nff = nf.frame; frame.left = (int) (of.left + (nff.left - of.left) * animation.getAnimatedFraction()); frame.top = (int) (of.top + (nff.top - of.top) * animation.getAnimatedFraction()); frame.right = frame.left + (int) (of.width() + (nff.width() - of.width()) * animation.getAnimatedFraction()); frame.bottom = frame.top + (int) (of.height() + (nff.height() - of.height()) * animation.getAnimatedFraction()); v.layout(frame.left, frame.top, frame.right, frame.bottom); // v.layout(nf.frame.left, nf.frame.top, nf.frame.right, // nf.frame.bottom); // v.setAlpha((1 - alpha) * animation.getAnimatedFraction() // + alpha); } catch (NullPointerException e) { Logs.e(TAG, "Nullpointer exception"); e.printStackTrace(); animation.cancel(); } } }); anim.setInterpolator(new DecelerateInterpolator(2.0f)); return anim; } public void setDuration(int duration) { this.cellPositionTransitionAnimationDuration = duration; } @Override public LayoutChangeset getChangeSet() { return changeSet; } @Override public boolean isRunning() { return mIsRunning; } @Override public void onContainerTouchDown(MotionEvent event) { cancel(); } }