/* * Copyright 2013 Niek Haarman * * 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.listviewanimations.swinginadapters; import android.annotation.SuppressLint; import android.os.Build; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.GridView; import com.marshalchen.common.uimodule.listviewanimations.BaseAdapterDecorator; import com.marshalchen.common.uimodule.nineoldandroids.animation.Animator; import com.marshalchen.common.uimodule.nineoldandroids.animation.AnimatorSet; import com.marshalchen.common.uimodule.nineoldandroids.animation.ObjectAnimator; import com.marshalchen.common.uimodule.nineoldandroids.view.ViewHelper; /** * A {@link BaseAdapterDecorator} class which applies multiple {@link Animator}s at once to * views when they are first shown. The Animators applied include the animations * specified in {@link #getAnimators(android.view.ViewGroup, android.view.View)}, plus an alpha transition. */ public abstract class AnimationAdapter extends BaseAdapterDecorator { protected static final long DEFAULTANIMATIONDELAYMILLIS = 100; protected static final long DEFAULTANIMATIONDURATIONMILLIS = 300; private static final long INITIALDELAYMILLIS = 150; private static final String ALPHA = "alpha"; private final SparseArray<Animator> mAnimators; private long mAnimationStartMillis; private int mFirstAnimatedPosition; private int mLastAnimatedPosition; private boolean mHasParentAnimationAdapter; private boolean mShouldAnimate = true; private long mInitialDelayMillis = INITIALDELAYMILLIS; private long mAnimationDelayMillis = DEFAULTANIMATIONDELAYMILLIS; private long mAnimationDurationMillis = DEFAULTANIMATIONDURATIONMILLIS; public AnimationAdapter(final BaseAdapter baseAdapter) { super(baseAdapter); mAnimators = new SparseArray<Animator>(); mAnimationStartMillis = -1; mFirstAnimatedPosition = -1; mLastAnimatedPosition = -1; if (baseAdapter instanceof AnimationAdapter) { ((AnimationAdapter) baseAdapter).setHasParentAnimationAdapter(true); } } /** * Call this method to reset animation status on all views. The next time * {@link #notifyDataSetChanged()} is called on the base adapter, all views will * animate again. Will also call {@link #setShouldAnimate(boolean)} with a value of true. */ public void reset() { mAnimators.clear(); mFirstAnimatedPosition = -1; mLastAnimatedPosition = -1; mAnimationStartMillis = -1; mShouldAnimate = true; if (getDecoratedBaseAdapter() instanceof AnimationAdapter) { ((AnimationAdapter) getDecoratedBaseAdapter()).reset(); } } /** * Set whether to animate the {@link android.view.View}s or not. * @param shouldAnimate true if the Views should be animated. */ public void setShouldAnimate(final boolean shouldAnimate) { mShouldAnimate = shouldAnimate; } /** * Set the starting position for which items should animate. Given position will animate as well. * Will also call setShouldAnimate(true). * @param position the position. */ @SuppressWarnings("UnusedDeclaration") public void setShouldAnimateFromPosition(final int position) { mShouldAnimate = true; mFirstAnimatedPosition = position - 1; mLastAnimatedPosition = position - 1; } /** * Set the starting position for which items should animate as the first position which isn't currently visible on screen. * This call is also valid when the {@link android.view.View}s haven't been drawn yet. * Will also call setShouldAnimate(true). */ @SuppressWarnings("UnusedDeclaration") public void setShouldAnimateNotVisible() { if (getAbsListView() == null) { throw new IllegalStateException("Call setListView() on this AnimationAdapter before setShouldAnimateNotVisible()!"); } mShouldAnimate = true; mFirstAnimatedPosition = getAbsListView().getLastVisiblePosition(); mLastAnimatedPosition = getAbsListView().getLastVisiblePosition(); } @Override public final View getView(final int position, final View convertView, final ViewGroup parent) { if (!mHasParentAnimationAdapter) { if (getAbsListView() == null) { throw new IllegalStateException("Call setListView() on this AnimationAdapter before setAdapter()!"); } if (convertView != null) { cancelExistingAnimation(convertView); } } View itemView = super.getView(position, convertView, parent); if (!mHasParentAnimationAdapter) { animateViewIfNecessary(position, itemView, parent); } return itemView; } private void cancelExistingAnimation(final View convertView) { int hashCode = convertView.hashCode(); Animator animator = mAnimators.get(hashCode); if (animator != null) { animator.end(); mAnimators.remove(hashCode); } } private void animateViewIfNecessary(final int position, final View view, final ViewGroup parent) { boolean isMeasuringGridViewItem = getAbsListView() instanceof GridView && parent.getHeight() == 0; if (position > mLastAnimatedPosition && mShouldAnimate && !isMeasuringGridViewItem) { if (mFirstAnimatedPosition == -1) { mFirstAnimatedPosition = position; } animateView(parent, view); mLastAnimatedPosition = position; } } private void animateView(final ViewGroup parent, final View view) { if (mAnimationStartMillis == -1) { mAnimationStartMillis = System.currentTimeMillis(); } ViewHelper.setAlpha(view, 0); Animator[] childAnimators; if (mDecoratedBaseAdapter instanceof AnimationAdapter) { childAnimators = ((AnimationAdapter) mDecoratedBaseAdapter).getAnimators(parent, view); } else { childAnimators = new Animator[0]; } Animator[] animators = getAnimators(parent, view); Animator alphaAnimator = ObjectAnimator.ofFloat(view, ALPHA, 0, 1); AnimatorSet set = new AnimatorSet(); set.playTogether(concatAnimators(childAnimators, animators, alphaAnimator)); set.setStartDelay(calculateAnimationDelay()); set.setDuration(getAnimationDurationMillis()); set.start(); mAnimators.put(view.hashCode(), set); } private Animator[] concatAnimators(final Animator[] childAnimators, final Animator[] animators, final Animator alphaAnimator) { Animator[] allAnimators = new Animator[childAnimators.length + animators.length + 1]; int i; for (i = 0; i < animators.length; ++i) { allAnimators[i] = animators[i]; } for (Animator childAnimator : childAnimators) { allAnimators[i] = childAnimator; ++i; } allAnimators[allAnimators.length - 1] = alphaAnimator; return allAnimators; } @SuppressLint("NewApi") private long calculateAnimationDelay() { long delay; int lastVisiblePosition = getAbsListView().getLastVisiblePosition(); int firstVisiblePosition = getAbsListView().getFirstVisiblePosition(); int numberOfItemsOnScreen = lastVisiblePosition - firstVisiblePosition; int numberOfAnimatedItems = mLastAnimatedPosition - mFirstAnimatedPosition; if (numberOfItemsOnScreen + 1 < numberOfAnimatedItems) { delay = getAnimationDelayMillis(); if (getAbsListView() instanceof GridView && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { delay += getAnimationDelayMillis() * ((mLastAnimatedPosition + 1) % ((GridView) getAbsListView()).getNumColumns()); } } else { long delaySinceStart = (mLastAnimatedPosition - mFirstAnimatedPosition + 1) * getAnimationDelayMillis(); delay = mAnimationStartMillis + getInitialDelayMillis() + delaySinceStart - System.currentTimeMillis(); } return Math.max(0, delay); } /** * Set whether this AnimationAdapter is encapsulated by another * AnimationAdapter. When this is set to true, this AnimationAdapter does * not apply any animations to the views. Should not be set explicitly, the * AnimationAdapter class manages this by itself. */ public void setHasParentAnimationAdapter(final boolean hasParentAnimationAdapter) { mHasParentAnimationAdapter = hasParentAnimationAdapter; } /** * Get the delay in milliseconds before the first animation should start. Defaults to {@value #INITIALDELAYMILLIS}. */ protected long getInitialDelayMillis() { return mInitialDelayMillis; } /** * Set the delay in milliseconds before the first animation should start. Defaults to {@value #INITIALDELAYMILLIS}. * @param delayMillis the time in milliseconds. */ public void setInitialDelayMillis(final long delayMillis) { mInitialDelayMillis = delayMillis; } /** * Get the delay in milliseconds before an animation of a view should start. Defaults to {@value #DEFAULTANIMATIONDELAYMILLIS}. */ protected long getAnimationDelayMillis() { return mAnimationDelayMillis; } /** * Set the delay in milliseconds before an animation of a view should start. Defaults to {@value #DEFAULTANIMATIONDELAYMILLIS}. * @param delayMillis the time in milliseconds. */ @SuppressWarnings("UnusedDeclaration") public void setAnimationDelayMillis(final long delayMillis) { mAnimationDelayMillis = delayMillis; } /** * Get the duration of the animation in milliseconds. Defaults to {@value #DEFAULTANIMATIONDURATIONMILLIS}. */ protected long getAnimationDurationMillis() { return mAnimationDurationMillis; } /** * Set the duration of the animation in milliseconds. Defaults to {@value #DEFAULTANIMATIONDURATIONMILLIS}. * @param durationMillis the time in milliseconds. */ @SuppressWarnings("UnusedDeclaration") public void setAnimationDurationMillis(final long durationMillis) { mAnimationDurationMillis = durationMillis; } /** * Get the Animators to apply to the views. In addition to the returned * Animators, an alpha transition will be applied to the view. * * @param parent * The parent of the view * @param view * The view that will be animated, as retrieved by getView() */ public abstract Animator[] getAnimators(ViewGroup parent, View view); }