RecyclerView.ItemAnimator终极解读(三)--继承DefaultItemAnimator实现自定义动画

DefaultItemAnimator是Android OS中一个默认的RecyclerView动画实现类,如果产品需求没有特别复杂的动画要求,可以使用DefaultItemAnimator实现简单的动画效果。DefaultItemAnimator动画的实现流程和原理已经在上两节中做过简单介绍,如果还没有看过的童鞋,最好先打眼扫一下之前两节的内容,有助于理解。附上链接地址:

RecyclerView.ItemAnimator终极解读(一)--RecyclerView源码解析

RecyclerView.ItemAnimator终极解读(二)--SimpleItemAnimator和DefaultItemAnimator源码解析

这一节,我们主要通过继承DefaultItemAnimator来实现稍微复杂一点的自定义动画。效果如下两张图所示:

     

如上两张图所示:每点击一个item时,更新所点击item的背景颜色和文本信息。 第一张图是没有添加动画效果, 第二张图是添加自定义动画之后的效果。

主要代码就是在自定义的动画类MyDefaultItemAnimator.java中, 如下所示:

package material.danny_jiang.com.mydefaultitemanimator;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.support.annotation.NonNull;
import android.support.v4.util.ArrayMap;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.List;

/**
 * Created by axing on 16/5/25.
 */
public class MyDefaultItemAnimator extends DefaultItemAnimator {
    private static final String TAG = "MyDefaultItemAnimator";

    // 定义动画执行时的加速度
    private AccelerateInterpolator mAccelerateInterpolator = new AccelerateInterpolator();
    private DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();

    ArgbEvaluator mColorEvaluator = new ArgbEvaluator();

    /**
     * 定义正在执行Animator的ViewHolder的Map集合
     * 此集合会保存用户点击的ViewHolder对象,目的在于当用户不停的点击某一item时
     * 会先判断此ViewHolder种的itemView动画是否正在执行,如果正在执行则停止
     */
    private ArrayMap<RecyclerView.ViewHolder, AnimatorInfo> mAnimatorMap = new ArrayMap<>();

    /**
     * 复写canReuseUpdatedViewHolder方法并返回true,通知RecyclerView在执行动画时可以复用ViewHolder对象
     * @param viewHolder
     * @return
     */
    @Override
    public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
        return true;
    }

    /**
     * 自定义getItemHolderInfo方法,将ViewHolder中的背景颜色和TextView的文本信息传入ColorTextInfo中
     * @param viewHolder
     * @param info
     * @return
     */
    @NonNull
    private ItemHolderInfo getItemHolderInfo(MyViewHolder viewHolder, ColorTextInfo info) {
        //获取当前正在操作的ViewHolder对象
        final MyViewHolder myHolder = viewHolder;
        //获取ViewHolder中itemView背景颜色
        final int bgColor = ((ColorDrawable) myHolder.container.getBackground()).getColor();
        //将背景颜色和TextView的文本信息赋值给ColorTextInfo对象的color和text变量
        info.color = bgColor;
        info.text = (String) myHolder.textView.getText();
        return info;
    }

    /**
     * 通过ViewHolder对象获取动画执行之前itemView中的背景颜色和文本信息
     * 初始化ColorTextInfo对象,并将背景颜色和文本信息进行赋值
     * @return
     */
    @NonNull
    @Override
    public ItemHolderInfo recordPreLayoutInformation(RecyclerView.State state,
                                                     RecyclerView.ViewHolder viewHolder, int changeFlags, List<Object> payloads) {
        Log.e(TAG, "recordPreLayoutInformation: " + viewHolder.toString());
        ColorTextInfo info = (ColorTextInfo) super.recordPreLayoutInformation(state, viewHolder,
                changeFlags, payloads);
        return getItemHolderInfo((MyViewHolder) viewHolder, info);
    }

    /**
     * 通过ViewHolder对象获取动画执行之后itemView中的背景颜色和文本信息
     * 初始化ColorTextInfo对象,并将背景颜色和文本信息进行赋值
     * @return
     */
    @NonNull
    @Override
    public ItemHolderInfo recordPostLayoutInformation(@NonNull RecyclerView.State state,
                                                      @NonNull RecyclerView.ViewHolder viewHolder) {
        Log.e(TAG, "recordPostLayoutInformation: " + viewHolder.toString());
        ColorTextInfo info = (ColorTextInfo) super.recordPostLayoutInformation(state, viewHolder);
        return getItemHolderInfo((MyViewHolder) viewHolder, info);
    }

    /**
     * 复写obtainHolderInfo,返回自定义的ItemHolderInfo对象
     * @return
     */
    @Override
    public ItemHolderInfo obtainHolderInfo() {
        Log.e(TAG, "obtainHolderInfo: ");
        return new ColorTextInfo();
    }

    /**
     * 自定义ItemHolderInfo对象,持有两个变量,依次来表示每一个Item的背景颜色和文本信息
     */
    private class ColorTextInfo extends ItemHolderInfo {
        int color;
        String text;
    }

    /**
     * 创建执行animateChange的动画Info对象,内部封装了所需要执行一个动画类的相关信息
     * 起始alpha属性动画,和起始旋转属性动画
     */
    private class AnimatorInfo {
        Animator overallAnim;
        ObjectAnimator fadeToBlackAnim, fadeFromBlackAnim, oldTextRotator, newTextRotator;

        public AnimatorInfo(Animator overallAnim,
                            ObjectAnimator fadeToBlackAnim, ObjectAnimator fadeFromBlackAnim,
                            ObjectAnimator oldTextRotator, ObjectAnimator newTextRotator) {
            this.overallAnim = overallAnim;
            this.fadeToBlackAnim = fadeToBlackAnim;
            this.fadeFromBlackAnim = fadeFromBlackAnim;
            this.oldTextRotator = oldTextRotator;
            this.newTextRotator = newTextRotator;
        }
    }

    /**
     * Custom change animation. Fade to black on the container background, then back
     * up to the new bg coolor. Meanwhile, the text rotates, switching along the way.
     * If a new change animation occurs on an item that is currently animating
     * a change, we stop the previous change and start the new one where the old
     * one left off.
     * 真正的执行change动画的方法:
     * 通过传入的preInfo和postInfo,分别将动画前后的背景色和文本信息设置到alpha属性动画和旋转属性动画中
     */
    @Override
    public boolean animateChange(@NonNull final RecyclerView.ViewHolder oldHolder,
                                 @NonNull final RecyclerView.ViewHolder newHolder,
                                 @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
        Log.e(TAG, "animateChange: ");

        if (oldHolder != newHolder) {
            //第一次显示所有的RecyclerView时,新旧ViewHolder是不相等的
            return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
        }

        final MyViewHolder viewHolder = (MyViewHolder) newHolder;

        // 获取动画前后的背景色和文本信息
        ColorTextInfo oldInfo = (ColorTextInfo) preInfo;
        ColorTextInfo newInfo = (ColorTextInfo) postInfo;
        int oldColor = oldInfo.color;
        int newColor = newInfo.color;
        final String oldText = oldInfo.text;
        final String newText = newInfo.text;

        // 获取需要被执行动画的View视图对象
        LinearLayout newContainer = viewHolder.container;
        final TextView newTextView = viewHolder.textView;

        // 从mAnimatorMap缓存中查找当前newHolder对应的itemView动画是否在执行中,如果是则终止动画
        AnimatorInfo runningInfo = mAnimatorMap.get(newHolder);
        long prevAnimPlayTime = 0;
        boolean firstHalf = false;
        if (runningInfo != null) {
            firstHalf = runningInfo.oldTextRotator != null &&
                    runningInfo.oldTextRotator.isRunning();
            prevAnimPlayTime = firstHalf ?
                    runningInfo.oldTextRotator.getCurrentPlayTime() :
                    runningInfo.newTextRotator.getCurrentPlayTime();
            runningInfo.overallAnim.cancel();
        }

        // 初始化背景颜色渐变的属性动画
        ObjectAnimator fadeToBlack = null, fadeFromBlack;
        if (runningInfo == null || firstHalf) {
            int startColor = oldColor;
            if (runningInfo != null) {
                startColor = (Integer) runningInfo.fadeToBlackAnim.getAnimatedValue();
            }
            fadeToBlack = ObjectAnimator.ofInt(newContainer, "backgroundColor",
                    startColor, Color.BLACK);
            fadeToBlack.setEvaluator(mColorEvaluator);
            if (runningInfo != null) {
                fadeToBlack.setCurrentPlayTime(prevAnimPlayTime);
            }
        }

        fadeFromBlack = ObjectAnimator.ofInt(newContainer, "backgroundColor",
                Color.BLACK, newColor);
        fadeFromBlack.setEvaluator(mColorEvaluator);
        if (runningInfo != null && !firstHalf) {
            fadeFromBlack.setCurrentPlayTime(prevAnimPlayTime);
        }

        AnimatorSet bgAnim = new AnimatorSet();
        if (fadeToBlack != null) {
            bgAnim.playSequentially(fadeToBlack, fadeFromBlack);
        } else {
            bgAnim.play(fadeFromBlack);
        }

        // 初始化旋转的属性动画
        ObjectAnimator oldTextRotate = null, newTextRotate;
        if (runningInfo == null || firstHalf) {
            oldTextRotate = ObjectAnimator.ofFloat(newTextView, View.ROTATION_X, 0, 90);
            oldTextRotate.setInterpolator(mAccelerateInterpolator);
            if (runningInfo != null) {
                oldTextRotate.setCurrentPlayTime(prevAnimPlayTime);
            }
            oldTextRotate.addListener(new AnimatorListenerAdapter() {
                boolean mCanceled = false;
                @Override
                public void onAnimationStart(Animator animation) {
                    newTextView.setText(oldText);
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                    mCanceled = true;
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    if (!mCanceled) {
                        //old动画执行之后,需要重新设置文本信息
                        newTextView.setText(newText);
                    }
                }
            });
        }

        newTextRotate = ObjectAnimator.ofFloat(newTextView, View.ROTATION_X, -90, 0);
        newTextRotate.setInterpolator(mDecelerateInterpolator);
        if (runningInfo != null && !firstHalf) {
            newTextRotate.setCurrentPlayTime(prevAnimPlayTime);
        }

        AnimatorSet textAnim = new AnimatorSet();
        if (oldTextRotate != null) {
            textAnim.playSequentially(oldTextRotate, newTextRotate);
        } else {
            textAnim.play(newTextRotate);
        }

        AnimatorSet changeAnim = new AnimatorSet();
        changeAnim.playTogether(bgAnim, textAnim);
        changeAnim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                dispatchAnimationFinished(newHolder);
                mAnimatorMap.remove(newHolder);
            }
        });
        changeAnim.start();

        AnimatorInfo runningAnimInfo = new AnimatorInfo(changeAnim, fadeToBlack, fadeFromBlack,
                oldTextRotate, newTextRotate);
        mAnimatorMap.put(newHolder, runningAnimInfo);

        return true;
    }

    @Override
    public void endAnimation(RecyclerView.ViewHolder item) {
        super.endAnimation(item);
        if (!mAnimatorMap.isEmpty()) {
            final int numRunning = mAnimatorMap.size();
            for (int i = numRunning; i >= 0; i--) {
                if (item == mAnimatorMap.keyAt(i)) {
                    mAnimatorMap.valueAt(i).overallAnim.cancel();
                }
            }
        }
    }

    @Override
    public boolean isRunning() {
        return super.isRunning() || !mAnimatorMap.isEmpty();
    }

    @Override
    public void endAnimations() {
        super.endAnimations();
        if (!mAnimatorMap.isEmpty()) {
            final int numRunning = mAnimatorMap.size();
            for (int i = numRunning; i >= 0; i--) {
                mAnimatorMap.valueAt(i).overallAnim.cancel();
            }
        }
    }
}

每段代码的含义的用途都已经在代码中添加了相应的注释,请耐得住寂寞,仔细查看^_^

自定义ItemAnimator实现好之后,剩下的就是初始化RecyclerView,并通过setItemAnimator方法将自定义ItemAnimator对象传递给RecyclerView。

MainActivity.java中的相关代码如下:

recyclerView = ((RecyclerView) findViewById(R.id.recyclerview));
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new RecyclerAdapter(this, recyclerView));
recyclerView.setItemAnimator(new MyDefaultItemAnimator());

RecyclerAdapter.java代码如下:

@Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        final MyViewHolder myHolder = (MyViewHolder) holder;
        int color = mColors.get(position);
        myHolder.container.setBackgroundColor(color);
        myHolder.textView.setText("#" + Integer.toHexString(color));
    }

    @Override
    public int getItemCount() {
        return mColors.size();
    }
private View.OnClickListener mItemAction = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            changeItem(v);
        }
    };

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View container = mainActivity.getLayoutInflater().inflate(R.layout.item_layout, parent, false);
        container.setOnClickListener(mItemAction);
        return new MyViewHolder(container);
    }

    private int generateColor() {
        int red = ((int) (Math.random() * 200));
        int green = ((int) (Math.random() * 200));
        int blue = ((int) (Math.random() * 200));
        return Color.rgb(red, green, blue);
    }
private void generateData() {
        for (int i = 0; i < 100; ++i) {
            mColors.add(generateColor());
        }
    }
private void changeItem(View view) {
        int position = mRecyclerView.getChildAdapterPosition(view);
        if (position != RecyclerView.NO_POSITION) {
            int color = generateColor();
            mColors.set(position, color);
            notifyItemChanged(position);
        }
    }

MyViewHolder.java代码如下:

package material.danny_jiang.com.mydefaultitemanimator;

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

/**
 * Created by axing on 16/5/25.
 */
class MyViewHolder extends RecyclerView.ViewHolder {
    public TextView textView;
    public LinearLayout container;

    public MyViewHolder(View v) {
        super(v);
        container = (LinearLayout) v;
        textView = (TextView) v.findViewById(R.id.textview);
    }

    @Override
    public String toString() {
        return super.toString() + " \"" + textView.getText() + "\"";
    }
}

时间: 2024-08-28 12:45:26

RecyclerView.ItemAnimator终极解读(三)--继承DefaultItemAnimator实现自定义动画的相关文章

SD3.0协议解读三

SD卡功能描述 所有主机和SD卡间的通信都是由主机控制的,这和USB是一致的,例如:U盘并没有主动通知USB控制器的能力,USB鼠标也没有主动通知USB控制器的能力,当然,SD卡也是没有主动通知SD控制器的能力的. 主机发送的命令有两种,一种是一对多,另一种自然是一对一了,他们分别是: 1.广播命令:广播命令发送给所有挂在SD总线上的SD卡,有一些广播命令需要SD卡作出响应. 2.寻址(点对点)命令:寻址命令只发送给具有相应地址的卡,并需要找到的那张卡返回一个响应. SD卡有两种模式,一种是卡识

单点登录CAS使用记(三):实现自定义验证用户登录

问题: CAS自带的用户验证逻辑太过简单,如何像正常网站一样,通过验证DB中的用户数据,来验证用户以及密码的合法性呢? 方案1:CAS默认的JDBC扩展方案: CAS自带了两种简单的通过JDBC方式验证用户的处理器. 1.QueryDatabaseAuthenticationHandler 2.SearchModeSearchDatabaseAuthenticationHandler 这两个处理类位于cas-server-support-jdbc这个扩展工程下. 第一步:改写用户验证处理器 打开

继承中的自定义构造方法

1.继承中的自定义构造方法 不能在子类访问父类私有变量 @interface Person : NSObject @property int age; - (id)initWithAge:(int)age; @end @interface Student : Person @property NSString *name; - (id)initWithAge:(int)age andName:(NSString *)name; @end @implementation Student - (id

JAVA之旅(三十四)——自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫

JAVA之旅(三十四)--自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫 我们接着来说网络编程,TCP 一.自定义服务端 我们直接写一个服务端,让本机去连接,可以看到什么样的效果 package com.lgl.socket; import java.io.IOException; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; publ

ios 继承UIView实现自定义视图——实现画图

主要的原理包括: 继承UIView ,重载drawrect和重载触摸事件 待实现的功能还有,路径数组保存等. 用可变数据保存path路径 画曲线是通过二次贝塞尔曲线实现的 这里可以得到画图的UIImage对象 UIGraphicsBeginImageContext(self.bounds.size); [self.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage *result=UIGraphicsGetImageFrom

(三十九)android动画 Animation四大属性 详解(转载:http://www.android100.org/html/201304/25/2295.html)

一.Animation主要有四大属性,分别是淡入淡出,绕轴旋转,变化大小,位移变化 二.四大属性的共同的方法 1.setDuration(long durationMills):设置动画持续的时间(单位:毫秒) 2.setFillAfter(boolean fillAfter):如果fillAfter的值为true,则动画执行后看,控件将停留在执行结束的状态 3.setFillBefore(boolean fillBefore):如果fillBefore的值为true,则动画执行后看,控件将停留

【Android进度条】三种方式实现自定义圆形进度条ProgressBar

一.通过动画实现 定义res/anim/loading.xml如下: [html] view plaincopyprint? <?xml version="1.0" encoding="UTF-8"?> <animation-list android:oneshot="false" xmlns:android="http://schemas.android.com/apk/res/android"> &

jquery中的三组基础动画以及自定义动画

Jquery基础三组动画 1.show(),hide(),toggle() 改变宽高透明度 2.fadeIn(),fadeout(), fadeToggle() 改变透明度(淡入,淡出效果) 3.slideDown(),slideUp(),slideToggle() 改变高度 语法: show(time,callback) time时间,单位是毫秒 回调函数,当动画执行完成后执行 自定义动画 animate 参数1:比传参数 ,样式对象 参数2:动画时间,默认 400ms (fast,norma

android v7兼容包RecyclerView的使用(三)——布局管理器的使用

前两篇文章 android v7兼容包RecyclerView的使用(二) android v7兼容包RecyclerView的使用(一) 介绍了RecyclerView的基本用法以及与它相关的重要的几个类,本篇文章介绍布局管理器的具体用法. 为了演示布局管理器的使用,找了很多个例子,都没有找到感觉合适的例子,后来google了一把,发现了一个比较适合说明问题的例子.所以就拿该例子来解释吧. 在演示布局管理器前,我们先把UI部分搭建完成.由于使用到了V7兼容包的另一个包CardView,所以在这