/* * Copyright 2017 Bartosz Lipinski * * 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.bartoszlipinski.constraint; import android.animation.TimeInterpolator; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.support.constraint.ConstraintLayout; import android.support.constraint.Group; import android.support.transition.Fade; import android.support.transition.Transition; import android.support.transition.TransitionManager; import android.support.transition.TransitionSet; import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.util.AttributeSet; import android.view.View; import com.bartoszlipinski.constraint.internal.Preconditions; import static com.bartoszlipinski.constraint.internal.Utils.getConstraintLayoutParent; import static com.bartoszlipinski.constraint.internal.Utils.notNull; public class StaggeredAnimationGroup extends Group { @VisibleForTesting static final int DEFAULT_PARTIAL_DURATION = 250; @VisibleForTesting static final int DEFAULT_PARTIAL_TRANSITION_DELAY = 50; @VisibleForTesting static final TimeInterpolator DEFAULT_PARTIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); @VisibleForTesting static PartialTransitionFactory defaultPartialTransitionFactory = new PartialTransitionFactory() { @NonNull @Override public Transition createPartialTransition(boolean isShowing, int viewId, int indexInTransition) { return new Fade(); } }; @VisibleForTesting static OnTransitionPreparedListener defaultOnPreparedListener = new OnTransitionPreparedListener() { @NonNull @Override public TransitionSet onStaggeredTransitionPrepared(@NonNull TransitionSet transitionSet, boolean isShowing, boolean inReversedOrder) { return transitionSet; } }; @VisibleForTesting int partialDelay = DEFAULT_PARTIAL_TRANSITION_DELAY; @VisibleForTesting int partialDuration = DEFAULT_PARTIAL_DURATION; @VisibleForTesting TimeInterpolator partialInterpolator = DEFAULT_PARTIAL_INTERPOLATOR; @VisibleForTesting PartialTransitionFactory partialTransitionFactory = defaultPartialTransitionFactory; @VisibleForTesting OnTransitionPreparedListener onPreparedListener = defaultOnPreparedListener; public StaggeredAnimationGroup(Context context) { super(context); } public StaggeredAnimationGroup(Context context, AttributeSet attrs) { super(context, attrs); } public StaggeredAnimationGroup(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @VisibleForTesting final int[] filterNonZeroIds(int[] allIds) { int nonZeroIdsCount = 0; for (int id : allIds) { if (id != 0) { nonZeroIdsCount++; } } if (nonZeroIdsCount == allIds.length) { return allIds; } int[] groupIds = new int[nonZeroIdsCount]; for (int i = 0; i < nonZeroIdsCount; i++) { groupIds[i] = allIds[i]; } return groupIds; } @VisibleForTesting final Transition prepareStaggeredTransition(boolean isShowing, boolean inReversedOrder) { TransitionSet staggeredTransition = new TransitionSet(); int[] nonZeroIds = filterNonZeroIds(mIds); if (inReversedOrder) { for (int i = nonZeroIds.length - 1, iteration = 0; i >= 0; i--, iteration++) { int id = nonZeroIds[i]; Transition basePartialTransition = preparePartialTransition(isShowing, id, iteration); addTransitionToStaggeredTransition( basePartialTransition, staggeredTransition, id, iteration); } } else { for (int iteration = 0; iteration < nonZeroIds.length; iteration++) { int id = nonZeroIds[iteration]; Transition basePartialTransition = preparePartialTransition(isShowing, id, iteration); addTransitionToStaggeredTransition( basePartialTransition, staggeredTransition, id, iteration); } } return onStaggeredTransitionReady(staggeredTransition, isShowing, inReversedOrder); } @VisibleForTesting final void addTransitionToStaggeredTransition(Transition basePartialTransition, TransitionSet staggeredTransition, int viewId, int indexInTransition) { Transition partialTransition = applyStaggeredTransitionParams(basePartialTransition, viewId, indexInTransition); staggeredTransition.addTransition(partialTransition); } @VisibleForTesting final Transition applyStaggeredTransitionParams(Transition partialTransition, int viewId, int indexInTransition) { partialTransition.setStartDelay(indexInTransition * partialDelay); partialTransition.addTarget(viewId); return partialTransition; } @VisibleForTesting final Transition preparePartialTransition(boolean isShowing, int id, int indexInTransition) { return partialTransitionFactory.createPartialTransition(isShowing, id, indexInTransition) .setDuration(partialDuration) .setInterpolator(partialInterpolator); } @VisibleForTesting final TransitionSet onStaggeredTransitionReady(TransitionSet transition, boolean isShowing, boolean inReversedOrder) { return onPreparedListener.onStaggeredTransitionPrepared(transition, isShowing, inReversedOrder); } public final void show() { show(false); } public final void show(boolean inReversedOrder) { ConstraintLayout parent = getConstraintLayoutParent(this); if (notNull(parent)) { Transition transition = prepareStaggeredTransition(true, inReversedOrder); TransitionManager.beginDelayedTransition(parent, transition); setVisibility(View.VISIBLE); } } public final void hide() { hide(false); } public final void hide(boolean inReversedOrder) { ConstraintLayout parent = getConstraintLayoutParent(this); if (notNull(parent)) { Transition transition = prepareStaggeredTransition(false, inReversedOrder); TransitionManager.beginDelayedTransition(parent, transition); setVisibility(View.GONE); } } public final void setPartialTransitionFactory(@NonNull PartialTransitionFactory factory) { Preconditions.checkNotNull(factory, "factory==null"); partialTransitionFactory = factory; } public final void clearPartialTransitionFactory() { partialTransitionFactory = defaultPartialTransitionFactory; } public final void setOnTransitionPreparedListener(@NonNull OnTransitionPreparedListener listener) { Preconditions.checkNotNull(listener, "listener==null"); onPreparedListener = listener; } public final void clearOnTransitionPreparedListener() { onPreparedListener = defaultOnPreparedListener; } public final void setPartialDuration(int partialDuration) { this.partialDuration = partialDuration; } public final void setPartialDelay(int partialDelay) { this.partialDelay = partialDelay; } public final void setPartialInterpolator(TimeInterpolator partialInterpolator) { this.partialInterpolator = partialInterpolator; } public interface PartialTransitionFactory { @NonNull Transition createPartialTransition(boolean show, int viewId, int indexInTransition); } public interface OnTransitionPreparedListener { @NonNull TransitionSet onStaggeredTransitionPrepared(@NonNull TransitionSet transitionSet, boolean show, boolean inReversedOrder); } }