All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.haarman.listviewanimations.swinginadapters.AnimationAdapter Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
/*
 * 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.haarman.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.haarman.listviewanimations.BaseAdapterDecorator;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;
import com.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(ViewGroup, 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 SparseArray 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(BaseAdapter baseAdapter) {
		super(baseAdapter);
		mAnimators = new SparseArray();

		mAnimationStartMillis = -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 = 0;
		mLastAnimatedPosition = -1;
		mAnimationStartMillis = -1;
		mShouldAnimate = true;

		if (getDecoratedBaseAdapter() instanceof AnimationAdapter) {
			((AnimationAdapter) getDecoratedBaseAdapter()).reset();
		}
	}

	/**
	 * Set whether to animate the {@link View}s or not.
	 * @param shouldAnimate true if the Views should be animated.
	 */
	public void setShouldAnimate(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.
	 */
	public void setShouldAnimateFromPosition(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 View}s haven't been drawn yet.
	 * Will also call setShouldAnimate(true).
	 */
	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(int position, View convertView, ViewGroup parent) {
		if (!mHasParentAnimationAdapter) {
			if (getAbsListView() == null) {
				throw new IllegalStateException("Call setListView() on this AnimationAdapter before setAdapter()!");
			}

			if (convertView != null) {
				cancelExistingAnimation(position, convertView);
			}
		}

		View itemView = super.getView(position, convertView, parent);

		if (!mHasParentAnimationAdapter) {
			animateViewIfNecessary(position, itemView, parent);
		}
		return itemView;
	}

	private void cancelExistingAnimation(int position, View convertView) {
		int hashCode = convertView.hashCode();
		Animator animator = mAnimators.get(hashCode);
		if (animator != null) {
			animator.end();
			mAnimators.remove(hashCode);
		}
	}

	private void animateViewIfNecessary(int position, View view, ViewGroup parent) {
		boolean isMeasuringGridViewItem = parent.getHeight() == 0;

		if (position > mLastAnimatedPosition && mShouldAnimate && !isMeasuringGridViewItem) {
			animateView(position, parent, view);
			mLastAnimatedPosition = position;
		}
	}

	private void animateView(int position, ViewGroup parent, 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(Animator[] childAnimators, Animator[] animators, 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 (int j = 0; j < childAnimators.length; ++j) {
			allAnimators[i] = childAnimators[j];
			++i;
		}

		allAnimators[allAnimators.length - 1] = alphaAnimator;
		return allAnimators;
	}

	@SuppressLint("NewApi")
	private long calculateAnimationDelay() {
		long delay;
		int numberOfItems = getAbsListView().getLastVisiblePosition() - getAbsListView().getFirstVisiblePosition();
		if (numberOfItems + 1 < mLastAnimatedPosition) {
			delay = getAnimationDelayMillis();

			if (getAbsListView() instanceof GridView && Build.VERSION.SDK_INT >= 11) {
				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(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(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.
	 */
	public void setAnimationDelayMillis(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.
	 */
	public void setAnimationDurationMillis(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);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy