content: /*
   * 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.nhaarman.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.nhaarman.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 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 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 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);
  }
  