使用LayoutAnimationController为RecyclerView添加动画

使用LayoutAnimationController为RecyclerView添加动画

@author:莫川

一、前言

为RecyclerView的Item添加动画有很多中方案,比如通过设置setItemAnimator来实现或者是通过遍历RecyclerView中的子View,然后分别对子View做动画。今天介绍一种更加简单的方式:通过LayoutAnimationController的方式,对RecyclerView的Item做动画。

二、效果以及源码

先看效果:首先是对LinearLayoutManager的RecyclerView。

  • 1.使用LayoutAnimationController的8种动画的播放效果
img
  • 2.使用GridLayoutAnimationController的8种动画的播放效果
img
img
Github源码:https://github.com/nuptboyzhb/RecyclerViewAnimation

三、实现方案

(1)LayoutAnimationController

比如实现从依次从左侧进入的动画效果,我们首先需要实现一个Item的动画效果,然后创建一个LayoutAnimationController对象,并设置每一个item播放动画的时间延时和item的播放顺序。

以从左侧进入为例,每个单独的Item的动画如下:

  • 1.Item动画

    ...
    /**
     * 从左侧进入,并带有弹性的动画
     *
     * @return
     */
    public static AnimationSet getAnimationSetFromLeft() {
        AnimationSet animationSet = new AnimationSet(true);
        TranslateAnimation translateX1 = new TranslateAnimation(RELATIVE_TO_SELF, -1.0f, RELATIVE_TO_SELF, 0.1f,
                RELATIVE_TO_SELF, 0, RELATIVE_TO_SELF, 0);
        translateX1.setDuration(300);
        translateX1.setInterpolator(new DecelerateInterpolator());
        translateX1.setStartOffset(0);

        TranslateAnimation translateX2 = new TranslateAnimation(RELATIVE_TO_SELF, 0.1f, RELATIVE_TO_SELF, -0.1f,
                RELATIVE_TO_SELF, 0, RELATIVE_TO_SELF, 0);
        translateX2.setStartOffset(300);
        translateX2.setInterpolator(new DecelerateInterpolator());
        translateX2.setDuration(50);

        TranslateAnimation translateX3 = new TranslateAnimation(RELATIVE_TO_SELF, -0.1f, RELATIVE_TO_SELF, 0f,
                RELATIVE_TO_SELF, 0, RELATIVE_TO_SELF, 0);
        translateX3.setStartOffset(350);
        translateX3.setInterpolator(new DecelerateInterpolator());
        translateX3.setDuration(50);

        animationSet.addAnimation(translateX1);
        animationSet.addAnimation(translateX2);
        animationSet.addAnimation(translateX3);
        animationSet.setDuration(400);

        return animationSet;
    }
    ...

为了让Item看起来有‘弹性’效果,animationSet添加了三个移动动画,分别是从左侧进入(-100%),移动到右侧的10%,然后在从右侧(10%)移动到左侧(-10%),最后再从(-10%)移动到原本的位置(0%)。这样就有了移动后的弹性效果。

  • 2.设置LayoutAnimationController的属性

2.1 设置ViewGroup的子View播放动画之间的offset。

/**
 * Sets the delay, as a fraction of the animation duration, by which the children's animations are offset.
 */
void setDelay(float delay)

2.2 设置ViewGroup的子View播放动画的顺序

/**
 * Sets the order used to compute the delay of each child's animation.
 */
void setOrder(int order)

setOrder可以取值为LayoutAnimationController.ORDER_NORMAL(正常顺序),LayoutAnimationController.ORDER_RANDOM(随机顺序)以及LayoutAnimationController.ORDER_REVERSE(逆序)。这里的demo设置的是正常顺序。

  • 3.播放动画
   /**
     * 播放RecyclerView动画
     *
     * @param animation
     * @param isReverse
     */
    public void playLayoutAnimation(Animation animation, boolean isReverse) {
        LayoutAnimationController controller = new LayoutAnimationController(animation);
        controller.setDelay(0.1f);
        controller.setOrder(isReverse ? LayoutAnimationController.ORDER_REVERSE : LayoutAnimationController.ORDER_NORMAL);

        mRecyclerView.setLayoutAnimation(controller);
        mRecyclerView.getAdapter().notifyDataSetChanged();
        mRecyclerView.scheduleLayoutAnimation();
    }

通过viewGroup.setLayoutAnimation设置layout动画。然后通知ViewGroup重新绘制,调用scheduleLayoutAnimation方法播放动画。

(2)GridLayoutAnimationController

上述方法针对的是线性的RecyclerView,也就是说RecyclerView的LayoutManager设置的是LinearLayoutManager.

    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_list);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setAdapter(new DemoRecyclerViewAdapter());
    }
    ...

而对于使用GridLayoutManager和StaggeredGridLayoutManager的RecyclerView来说,我们需要使用GridLayoutAnimationController,其他步骤与LayoutAnimationController一致。

    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_grid);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mRecyclerView = (StaggeredGridRecyclerView) findViewById(R.id.recycler_view);
        mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
        mStaggeredGridAdapter = new StaggeredGridAdapter();
        mStaggeredGridAdapter.setDataSet(mockData());
        mRecyclerView.setAdapter(mStaggeredGridAdapter);
    }

    ....

同样的,仍然使用之前的Animation创建GridLayoutAnimationController。

   ...
   /**
     * 播放动画
     *
     * @param animation
     * @param isReverse
     */
    public void playLayoutAnimation(Animation animation, boolean isReverse) {
        GridLayoutAnimationController controller = new GridLayoutAnimationController(animation);
        controller.setColumnDelay(0.2f);
        controller.setRowDelay(0.3f);
        controller.setOrder(isReverse ? LayoutAnimationController.ORDER_REVERSE : LayoutAnimationController.ORDER_NORMAL);

        mRecyclerView.setLayoutAnimation(controller);
        mRecyclerView.getAdapter().notifyDataSetChanged();
        mRecyclerView.scheduleLayoutAnimation();
    }
    ...

GridLayoutAnimationController的delay方法可以分别按照Column和Row维度进行设置。

本以为到此顺利结束。运行后发现,会Crash,log为:

...
E/AndroidRuntime( 7876): java.lang.ClassCastException: android.view.animation.LayoutAnimationController$AnimationParameters cannot be cast to android.view.animation.GridLayoutAnimationController$AnimationParameters
E/AndroidRuntime( 7876):    at android.view.animation.GridLayoutAnimationController.getDelayForView(GridLayoutAnimationController.java:299)
E/AndroidRuntime( 7876):    at android.view.animation.LayoutAnimationController.getAnimationForView(LayoutAnimationController.java:321)
E/AndroidRuntime( 7876):    at android.view.ViewGroup.bindLayoutAnimation(ViewGroup.java:4227)
E/AndroidRuntime( 7876):    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3272)
E/AndroidRuntime( 7876):    at android.view.View.draw(View.java:15618)
E/AndroidRuntime( 7876):    at android.support.v7.widget.RecyclerView.draw(RecyclerView.java:3869)
E/AndroidRuntime( 7876):    at android.view.View.updateDisplayListIfDirty(View.java:14495)
E/AndroidRuntime( 7876):    at android.view.View.getDisplayList(View.java:14524)
E/AndroidRuntime( 7876):    at android.view.View.draw(View.java:15315)
E/AndroidRuntime( 7876):    at android.view.ViewGroup.drawChild(ViewGroup.java:3536)
E/AndroidRuntime( 7876):    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3329)
E/AndroidRuntime( 7876):    at android.view.View.draw(View.java:15618)
E/AndroidRuntime( 7876):    at android.view.View.updateDisplayListIfDirty(View.java:14495)
E/AndroidRuntime( 7876):    at android.view.View.getDisplayList(View.java:14524)
...

为了解决这个问题,我们需要override RecyclerView的attachLayoutAnimationParameters方法:

package com.github.nuptboyzhb.recyclerviewanimation.grid;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.GridLayoutAnimationController;

/**
 * @version Created by haibozheng on 2016/12/9.
 * @Author Zheng Haibo
 * @Blog github.com/nuptboyzhb
 * @Company Alibaba Group
 * @Description StaggeredGridRecyclerView
 */
public class StaggeredGridRecyclerView extends RecyclerView {

    public StaggeredGridRecyclerView(Context context) {
        super(context);
    }

    public StaggeredGridRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public StaggeredGridRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * 支持GridLayoutManager以及StaggeredGridLayoutManager
     *
     * @param child
     * @param params
     * @param index
     * @param count
     */
    @Override
    protected void attachLayoutAnimationParameters(View child, ViewGroup.LayoutParams params,
                                                   int index, int count) {
        LayoutManager layoutManager = this.getLayoutManager();
        if (getAdapter() != null && (layoutManager instanceof GridLayoutManager
                || layoutManager instanceof StaggeredGridLayoutManager)) {

            GridLayoutAnimationController.AnimationParameters animationParams =
                    (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;

            if (animationParams == null) {
                animationParams = new GridLayoutAnimationController.AnimationParameters();
                params.layoutAnimationParameters = animationParams;
            }

            int columns = 0;
            if (layoutManager instanceof GridLayoutManager) {
                columns = ((GridLayoutManager) layoutManager).getSpanCount();
            } else {
                columns = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
            }

            animationParams.count = count;
            animationParams.index = index;
            animationParams.columnsCount = columns;
            animationParams.rowsCount = count / columns;

            final int invertedIndex = count - 1 - index;
            animationParams.column = columns - 1 - (invertedIndex % columns);
            animationParams.row = animationParams.rowsCount - 1 - invertedIndex / columns;

        } else {
            super.attachLayoutAnimationParameters(child, params, index, count);
        }
    }
}

更多动画效果,请参见Github源码:https://github.com/nuptboyzhb/RecyclerViewAnimation

四、总结

通过LayoutAnimationController或者GridLayoutAnimationController来实现RecyclerView的动画,非常简单,而且效果很好。该方式不仅可以应用于RecyclerView,而且还适用于ListView、LinearLayout、GridView等ViewGroup。比如,如下是作用在一个LinearLayout的效果。


linearlayout_demo


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容