Home | History | Annotate | Download | only in deskclock
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.deskclock;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.AnimatorSet;
     22 import android.animation.ObjectAnimator;
     23 import android.animation.PropertyValuesHolder;
     24 import android.support.annotation.NonNull;
     25 import android.support.v4.util.ArrayMap;
     26 import android.support.v7.widget.RecyclerView.State;
     27 import android.support.v7.widget.RecyclerView.ViewHolder;
     28 import android.support.v7.widget.SimpleItemAnimator;
     29 import android.view.View;
     30 
     31 import java.util.ArrayList;
     32 import java.util.List;
     33 import java.util.Map;
     34 
     35 import static android.view.View.TRANSLATION_Y;
     36 import static android.view.View.TRANSLATION_X;
     37 
     38 public class ItemAnimator extends SimpleItemAnimator {
     39 
     40     private final List<Animator> mAddAnimatorsList = new ArrayList<>();
     41     private final List<Animator> mRemoveAnimatorsList = new ArrayList<>();
     42     private final List<Animator> mChangeAnimatorsList = new ArrayList<>();
     43     private final List<Animator> mMoveAnimatorsList = new ArrayList<>();
     44 
     45     private final Map<ViewHolder, Animator> mAnimators = new ArrayMap<>();
     46 
     47     @Override
     48     public boolean animateRemove(final ViewHolder holder) {
     49         endAnimation(holder);
     50 
     51         final float prevAlpha = holder.itemView.getAlpha();
     52 
     53         final Animator removeAnimator = ObjectAnimator.ofFloat(holder.itemView, View.ALPHA, 0f);
     54         removeAnimator.setDuration(getRemoveDuration());
     55         removeAnimator.addListener(new AnimatorListenerAdapter() {
     56             @Override
     57             public void onAnimationStart(Animator animator) {
     58                 dispatchRemoveStarting(holder);
     59             }
     60 
     61             @Override
     62             public void onAnimationEnd(Animator animator) {
     63                 animator.removeAllListeners();
     64                 mAnimators.remove(holder);
     65                 holder.itemView.setAlpha(prevAlpha);
     66                 dispatchRemoveFinished(holder);
     67             }
     68         });
     69         mRemoveAnimatorsList.add(removeAnimator);
     70         mAnimators.put(holder, removeAnimator);
     71         return true;
     72     }
     73 
     74     @Override
     75     public boolean animateAdd(final ViewHolder holder) {
     76         endAnimation(holder);
     77 
     78         final float prevAlpha = holder.itemView.getAlpha();
     79         holder.itemView.setAlpha(0f);
     80 
     81         final Animator addAnimator = ObjectAnimator.ofFloat(holder.itemView, View.ALPHA, 1f)
     82                 .setDuration(getAddDuration());
     83         addAnimator.addListener(new AnimatorListenerAdapter() {
     84             @Override
     85             public void onAnimationStart(Animator animator) {
     86                 dispatchAddStarting(holder);
     87             }
     88 
     89             @Override
     90             public void onAnimationEnd(Animator animator) {
     91                 animator.removeAllListeners();
     92                 mAnimators.remove(holder);
     93                 holder.itemView.setAlpha(prevAlpha);
     94                 dispatchAddFinished(holder);
     95             }
     96         });
     97         mAddAnimatorsList.add(addAnimator);
     98         mAnimators.put(holder, addAnimator);
     99         return true;
    100     }
    101 
    102     @Override
    103     public boolean animateMove(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
    104         endAnimation(holder);
    105 
    106         final int deltaX = toX - fromX;
    107         final int deltaY = toY - fromY;
    108         final long moveDuration = getMoveDuration();
    109 
    110         if (deltaX == 0 && deltaY == 0) {
    111             dispatchMoveFinished(holder);
    112             return false;
    113         }
    114 
    115         final View view = holder.itemView;
    116         final float prevTranslationX = view.getTranslationX();
    117         final float prevTranslationY = view.getTranslationY();
    118         view.setTranslationX(-deltaX);
    119         view.setTranslationY(-deltaY);
    120 
    121         final ObjectAnimator moveAnimator;
    122         if (deltaX != 0 && deltaY != 0) {
    123             final PropertyValuesHolder moveX = PropertyValuesHolder.ofFloat(TRANSLATION_X, 0f);
    124             final PropertyValuesHolder moveY = PropertyValuesHolder.ofFloat(TRANSLATION_Y, 0f);
    125             moveAnimator = ObjectAnimator.ofPropertyValuesHolder(holder.itemView, moveX, moveY);
    126         } else if (deltaX != 0) {
    127             final PropertyValuesHolder moveX = PropertyValuesHolder.ofFloat(TRANSLATION_X, 0f);
    128             moveAnimator = ObjectAnimator.ofPropertyValuesHolder(holder.itemView, moveX);
    129         } else {
    130             final PropertyValuesHolder moveY = PropertyValuesHolder.ofFloat(TRANSLATION_Y, 0f);
    131             moveAnimator = ObjectAnimator.ofPropertyValuesHolder(holder.itemView, moveY);
    132         }
    133 
    134         moveAnimator.setDuration(moveDuration);
    135         moveAnimator.setInterpolator(AnimatorUtils.INTERPOLATOR_FAST_OUT_SLOW_IN);
    136         moveAnimator.addListener(new AnimatorListenerAdapter() {
    137             @Override
    138             public void onAnimationStart(Animator animator) {
    139                 dispatchMoveStarting(holder);
    140             }
    141 
    142             @Override
    143             public void onAnimationEnd(Animator animator) {
    144                 animator.removeAllListeners();
    145                 mAnimators.remove(holder);
    146                 view.setTranslationX(prevTranslationX);
    147                 view.setTranslationY(prevTranslationY);
    148                 dispatchMoveFinished(holder);
    149             }
    150         });
    151         mMoveAnimatorsList.add(moveAnimator);
    152         mAnimators.put(holder, moveAnimator);
    153 
    154         return true;
    155     }
    156 
    157     @Override
    158     public boolean animateChange(@NonNull final ViewHolder oldHolder,
    159             @NonNull final ViewHolder newHolder, @NonNull ItemHolderInfo preInfo,
    160             @NonNull ItemHolderInfo postInfo) {
    161         endAnimation(oldHolder);
    162         endAnimation(newHolder);
    163 
    164         final long changeDuration = getChangeDuration();
    165         List<Object> payloads = preInfo instanceof PayloadItemHolderInfo
    166                 ? ((PayloadItemHolderInfo) preInfo).getPayloads() : null;
    167 
    168         if (oldHolder == newHolder) {
    169             final Animator animator = ((OnAnimateChangeListener) newHolder)
    170                     .onAnimateChange(payloads, preInfo.left, preInfo.top, preInfo.right,
    171                             preInfo.bottom, changeDuration);
    172             if (animator == null) {
    173                 dispatchChangeFinished(newHolder, false);
    174                 return false;
    175             }
    176             animator.addListener(new AnimatorListenerAdapter() {
    177                 @Override
    178                 public void onAnimationStart(Animator animator) {
    179                     dispatchChangeStarting(newHolder, false);
    180                 }
    181 
    182                 @Override
    183                 public void onAnimationEnd(Animator animator) {
    184                     animator.removeAllListeners();
    185                     mAnimators.remove(newHolder);
    186                     dispatchChangeFinished(newHolder, false);
    187                 }
    188             });
    189             mChangeAnimatorsList.add(animator);
    190             mAnimators.put(newHolder, animator);
    191             return true;
    192         } else if (!(oldHolder instanceof OnAnimateChangeListener) ||
    193                 !(newHolder instanceof OnAnimateChangeListener)) {
    194             // Both holders must implement OnAnimateChangeListener in order to animate.
    195             dispatchChangeFinished(oldHolder, true);
    196             dispatchChangeFinished(newHolder, true);
    197             return false;
    198         }
    199 
    200         final Animator oldChangeAnimator = ((OnAnimateChangeListener) oldHolder)
    201                 .onAnimateChange(oldHolder, newHolder, changeDuration);
    202         if (oldChangeAnimator != null) {
    203             oldChangeAnimator.addListener(new AnimatorListenerAdapter() {
    204                 @Override
    205                 public void onAnimationStart(Animator animator) {
    206                     dispatchChangeStarting(oldHolder, true);
    207                 }
    208 
    209                 @Override
    210                 public void onAnimationEnd(Animator animator) {
    211                     animator.removeAllListeners();
    212                     mAnimators.remove(oldHolder);
    213                     dispatchChangeFinished(oldHolder, true);
    214                 }
    215             });
    216             mAnimators.put(oldHolder, oldChangeAnimator);
    217             mChangeAnimatorsList.add(oldChangeAnimator);
    218         } else {
    219             dispatchChangeFinished(oldHolder, true);
    220         }
    221 
    222         final Animator newChangeAnimator = ((OnAnimateChangeListener) newHolder)
    223                 .onAnimateChange(oldHolder, newHolder, changeDuration);
    224         if (newChangeAnimator != null) {
    225             newChangeAnimator.addListener(new AnimatorListenerAdapter() {
    226                 @Override
    227                 public void onAnimationStart(Animator animator) {
    228                     dispatchChangeStarting(newHolder, false);
    229                 }
    230 
    231                 @Override
    232                 public void onAnimationEnd(Animator animator) {
    233                     animator.removeAllListeners();
    234                     mAnimators.remove(newHolder);
    235                     dispatchChangeFinished(newHolder, false);
    236                 }
    237             });
    238             mAnimators.put(newHolder, newChangeAnimator);
    239             mChangeAnimatorsList.add(newChangeAnimator);
    240         } else {
    241             dispatchChangeFinished(newHolder, false);
    242         }
    243 
    244         return true;
    245     }
    246 
    247     @Override
    248     public boolean animateChange(ViewHolder oldHolder,
    249             ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
    250         /* Unused */
    251         throw new IllegalStateException("This method should not be used");
    252     }
    253 
    254     @Override
    255     public void runPendingAnimations() {
    256         final AnimatorSet removeAnimatorSet = new AnimatorSet();
    257         removeAnimatorSet.playTogether(mRemoveAnimatorsList);
    258         mRemoveAnimatorsList.clear();
    259 
    260         final AnimatorSet addAnimatorSet = new AnimatorSet();
    261         addAnimatorSet.playTogether(mAddAnimatorsList);
    262         mAddAnimatorsList.clear();
    263 
    264         final AnimatorSet changeAnimatorSet = new AnimatorSet();
    265         changeAnimatorSet.playTogether(mChangeAnimatorsList);
    266         mChangeAnimatorsList.clear();
    267 
    268         final AnimatorSet moveAnimatorSet = new AnimatorSet();
    269         moveAnimatorSet.playTogether(mMoveAnimatorsList);
    270         mMoveAnimatorsList.clear();
    271 
    272         final AnimatorSet pendingAnimatorSet = new AnimatorSet();
    273         pendingAnimatorSet.addListener(new AnimatorListenerAdapter() {
    274             @Override
    275             public void onAnimationEnd(Animator animator) {
    276                 animator.removeAllListeners();
    277                 dispatchFinishedWhenDone();
    278             }
    279         });
    280         // Required order: removes, then changes & moves simultaneously, then additions. There are
    281         // redundant edges because changes or moves may be empty, causing the removes to incorrectly
    282         // play immediately.
    283         pendingAnimatorSet.play(removeAnimatorSet).before(changeAnimatorSet);
    284         pendingAnimatorSet.play(removeAnimatorSet).before(moveAnimatorSet);
    285         pendingAnimatorSet.play(changeAnimatorSet).with(moveAnimatorSet);
    286         pendingAnimatorSet.play(addAnimatorSet).after(changeAnimatorSet);
    287         pendingAnimatorSet.play(addAnimatorSet).after(moveAnimatorSet);
    288         pendingAnimatorSet.start();
    289     }
    290 
    291     @Override
    292     public void endAnimation(ViewHolder holder) {
    293         final Animator animator = mAnimators.get(holder);
    294 
    295         mAnimators.remove(holder);
    296         mAddAnimatorsList.remove(animator);
    297         mRemoveAnimatorsList.remove(animator);
    298         mChangeAnimatorsList.remove(animator);
    299         mMoveAnimatorsList.remove(animator);
    300 
    301         if (animator != null) {
    302             animator.end();
    303         }
    304 
    305         dispatchFinishedWhenDone();
    306     }
    307 
    308     @Override
    309     public void endAnimations() {
    310         final List<Animator> animatorList = new ArrayList<>(mAnimators.values());
    311         for (Animator animator : animatorList) {
    312             animator.end();
    313         }
    314         dispatchFinishedWhenDone();
    315     }
    316 
    317     @Override
    318     public boolean isRunning() {
    319         return !mAnimators.isEmpty();
    320     }
    321 
    322     private void dispatchFinishedWhenDone() {
    323         if (!isRunning()) {
    324             dispatchAnimationsFinished();
    325         }
    326     }
    327 
    328     @Override
    329     public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state,
    330             @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags,
    331             @NonNull List<Object> payloads) {
    332         final ItemHolderInfo itemHolderInfo = super.recordPreLayoutInformation(state, viewHolder,
    333                 changeFlags, payloads);
    334         if (itemHolderInfo instanceof PayloadItemHolderInfo) {
    335             ((PayloadItemHolderInfo) itemHolderInfo).setPayloads(payloads);
    336         }
    337         return itemHolderInfo;
    338     }
    339 
    340     @Override
    341     public ItemHolderInfo obtainHolderInfo() {
    342         return new PayloadItemHolderInfo();
    343     }
    344 
    345     @Override
    346     public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
    347             @NonNull List<Object> payloads) {
    348         final boolean defaultReusePolicy = super.canReuseUpdatedViewHolder(viewHolder, payloads);
    349         // Whenever we have a payload, this is an in-place animation.
    350         return !payloads.isEmpty() || defaultReusePolicy;
    351     }
    352 
    353     private static final class PayloadItemHolderInfo extends ItemHolderInfo {
    354         private final List<Object> mPayloads = new ArrayList<>();
    355 
    356         void setPayloads(List<Object> payloads) {
    357             mPayloads.clear();
    358             mPayloads.addAll(payloads);
    359         }
    360 
    361         List<Object> getPayloads() {
    362             return mPayloads;
    363         }
    364     }
    365 
    366     public interface OnAnimateChangeListener {
    367         Animator onAnimateChange(ViewHolder oldHolder, ViewHolder newHolder, long duration);
    368         Animator onAnimateChange(List<Object> payloads, int fromLeft, int fromTop, int fromRight,
    369                 int fromBottom, long duration);
    370     }
    371 }