Android---RecyclerView之动画(工具类)实现可展开列表

前言——项目说明

最近公司的项目需要实现一个列表的Item可展开收缩的效果,就是类似于QQ联系人中的那种效果。拿到这个需求之后,第一反应是用ExpandableListView,但是又想到RecyclerView这么强大,用它肯定也能实现,就想着可以定义父布局和子布局两种布局类型,再定义一个接口创建展开和隐藏两个监听方法,在展开时插入一条隐藏时删除一条,这样应该是可以实现的,但是这两种方案都有一个弊端,因为我们的后台接口把展示的所有信息都是放在同一个Bean里给我的,所以这样操作父类型和子类型的数据的时候还得自行分离,比较麻烦,所以我又在网上去寻找新的解决方案。

说来也巧,睡了一觉之后突然有了新的想法,Item中展开和收缩的两部分View还是作为一个整体定义在一个布局文件中,只要在点击的时候控制它显示隐藏就OK了啊!基于这个基本思路,借助强大的Google,最终找到了一种我觉得很简洁的解决方案,利用动画设置透明度来解决。感谢项目的原作者——Salomon BRYS,一个法国人!我是在stackoverflow上面看到这个问题的,这里把地址提供大家:https://stackoverflow.com/questions/27446051/recyclerview-animate-item-resize,进到这个页面然后找到下图这个地方:

点击去下载的时候有可能需要翻墙(我之前下载的时候是需要的),为了方便,我把代码上传到CSDN上了,需要的可以下载(没办法必须要选积分我就选了个最低的):

http://download.csdn.net/download/jarchie520/10195490

大家可以下载这份源码进行学习,鉴于我的项目是公司项目,所以无法提供源码给大家,只能截取部分关键代码进行讲解。

一、首先来看具体的实现效果,这里录了一个GIF的动态图:

二、代码说明

(一)、布局文件说明

将需要展开收缩的那部分布局的透明度在xml文件里默认设置为0,如图所示:

(二)、动画工具类说明(代码我基本上都添加了注释):

1、首先来看ExpandableViewHoldersUtil这个类,代码如下:

package com.jarchie.htgl.animation;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;

public class ExpandableViewHoldersUtil {

    //自定义处理列表中右侧图标,这里是一个旋转动画
    public static void rotateExpandIcon(final ImageView mImage, float from, float to) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            ValueAnimator valueAnimator = ValueAnimator.ofFloat(from, to);//属性动画
            valueAnimator.setDuration(500);
            valueAnimator.setInterpolator(new DecelerateInterpolator());
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    mImage.setRotation((Float) valueAnimator.getAnimatedValue());
                }
            });
            valueAnimator.start();
        }
    }

    //参数介绍:1、holder对象 2、展开部分的View,由holder.getExpandView()方法获取 3、animate参数为true,则有动画效果
    public static void openHolder(final RecyclerView.ViewHolder holder, final View expandView, final boolean animate) {
        if (animate) {
            expandView.setVisibility(View.VISIBLE);
            //改变高度的动画
            final Animator animator = ViewHolderAnimator.ofItemViewHeight(holder);
            //扩展的动画,结束后透明度动画开始
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    final ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(expandView, View.ALPHA, 1);
                    alphaAnimator.addListener(new ViewHolderAnimator.ViewHolderAnimatorListener(holder));
                    alphaAnimator.start();
                }
            });
            animator.start();
        } else { //为false时直接显示
            expandView.setVisibility(View.VISIBLE);
            expandView.setAlpha(1);
        }
    }

    //类似于打开的方法
    public static void closeHolder(final RecyclerView.ViewHolder holder, final View expandView, final boolean animate) {
        if (animate) {
            expandView.setVisibility(View.GONE);
            final Animator animator = ViewHolderAnimator.ofItemViewHeight(holder);
            expandView.setVisibility(View.VISIBLE);
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    expandView.setVisibility(View.GONE);
                    expandView.setAlpha(0);
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                    expandView.setVisibility(View.GONE);
                    expandView.setAlpha(0);
                }
            });
            animator.start();
        } else {
            expandView.setVisibility(View.GONE);
            expandView.setAlpha(0);
        }
    }

    //获取展开部分的View
    public interface Expandable {
        View getExpandView();
    }

    @SuppressWarnings("deprecation")
    public static class KeepOneHolder<VH extends RecyclerView.ViewHolder & Expandable> {
        //-1表示所有item是关闭状态,opend为pos值的表示pos位置的item为展开的状态
        private int opened = -1;

        /**
         * 此方法是在Adapter的onBindViewHolder()方法中调用
         *
         * @param holder holder对象
         * @param pos    下标
         */
        public void bind(VH holder, int pos) {
            if (pos == opened) //展开ExpandView
                ExpandableViewHoldersUtil.openHolder(holder, holder.getExpandView(), false);
            else //关闭ExpandView
                ExpandableViewHoldersUtil.closeHolder(holder, holder.getExpandView(), false);
        }

        /**
         * 响应ViewHolder的点击事件
         *
         * @param holder    holder对象
         * @param imageView 这里我传入了一个ImageView对象,为了处理图片旋转的动画,为了处理内部业务
         */
        @SuppressWarnings("unchecked")
        public void toggle(VH holder, ImageView imageView) {
            if (opened == holder.getPosition()) { //点击的就是打开的Item,则关闭item,并将opend置为-1
                opened = -1;
                ExpandableViewHoldersUtil.rotateExpandIcon(imageView, 180, 0);
                ExpandableViewHoldersUtil.closeHolder(holder, holder.getExpandView(), true);
            } else { //点击的是本来关闭的Item,则把opend值换成当前pos,把之前打开的Item给关掉
                int previous = opened;
                opened = holder.getPosition();
                ExpandableViewHoldersUtil.rotateExpandIcon(imageView, 0, 180);
                ExpandableViewHoldersUtil.openHolder(holder, holder.getExpandView(), true);
                //动画关闭之前打开的Item
                final VH oldHolder = (VH) ((RecyclerView) holder.itemView.getParent()).findViewHolderForPosition(previous);
                if (oldHolder != null)
                    ExpandableViewHoldersUtil.closeHolder(oldHolder, oldHolder.getExpandView(), true);
            }
        }
    }

}

这里我比Demo中多了一个操作ImageView的动画方法rotateExpandIcon(final ImageView mImage,float from,float to),这个是根据我的实际业务新增的,可以忽略不看。

先来看这个类中的内部类:KeepOneHolder这个类,它的泛型就是RecyclerView的ViewHolder,这是用来操作我们自己的RecyclerView的Adapter中的ViewHolder的,在这个类中定义了两个方法:bind()方法用来绑定控件,需要在我们自己的Adapter中的onBindViewHolder()方法中调用;toggle()方法用来处理ViewHolder的点击事件,控制可展开View的展开与收缩。

然后这个类中还定义了一个接口Expandable,用于返回展开部分的View,我们需要在我们自己的Adapter的ViewHolder中去实现这个接口,将需要展开收缩的View给return回来。

最后来看具体实现展开与收缩动画的openHolder()和closeHolder()方法,可以发现具体操作动画的代码就是下面这句

final Animator animator = ViewHolderAnimator.ofItemViewHeight(holder);

2、接着上面的来到了ViewHolderAnimator这个类,代码如下:

package com.jarchie.htgl.animation;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

public class ViewHolderAnimator {

    public static class ViewHolderAnimatorListener extends AnimatorListenerAdapter {
        private final RecyclerView.ViewHolder mHolder; //holder对象

        //设定在动画开始结束和取消状态下是否可以被回收
        public ViewHolderAnimatorListener(RecyclerView.ViewHolder holder) {
            mHolder = holder;
        }

        @Override
        public void onAnimationStart(Animator animation) { //开始时
            mHolder.setIsRecyclable(false);
        }

        @Override
        public void onAnimationEnd(Animator animation) { //结束时
            mHolder.setIsRecyclable(true);
        }

        @Override
        public void onAnimationCancel(Animator animation) { //取消时
            mHolder.setIsRecyclable(true);
        }
    }

    //设定在动画结束后View的宽度和高度分别为match_parent,warp_content
    public static class LayoutParamsAnimatorListener extends AnimatorListenerAdapter {
        private final View mView;
        private final int mParamsWidth;
        private final int mParamsHeight;

        public LayoutParamsAnimatorListener(View view, int paramsWidth, int paramsHeight) {
            mView = view;
            mParamsWidth = paramsWidth;
            mParamsHeight = paramsHeight;
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            final ViewGroup.LayoutParams params = mView.getLayoutParams();
            params.width = mParamsWidth;
            params.height = mParamsHeight;
            mView.setLayoutParams(params);
        }
    }

    //OpenHolder中动画的具体操作方法
    public static Animator ofItemViewHeight(RecyclerView.ViewHolder holder) {
        View parent = (View) holder.itemView.getParent();
        if (parent == null)
            throw new IllegalStateException("Cannot animate the layout of a view that has no parent");
        //测量扩展动画的起始高度和结束高度
        int start = holder.itemView.getMeasuredHeight();
        holder.itemView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(),
                View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
        int end = holder.itemView.getMeasuredHeight();
        final Animator animator = LayoutAnimator.ofHeight(holder.itemView, start, end); //具体的展开动画
        //设定该Item在动画开始结束和取消时能否被recycle
        animator.addListener(new ViewHolderAnimatorListener(holder));
        //设定结束时这个Item的宽高
        animator.addListener(new LayoutParamsAnimatorListener(holder.itemView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        return animator;
    }

}

ViewHolderAnimatorListener这个监听中设定动画在开始、结束和取消状态下是否可以被回收;在LayoutParamsAnimatorListener这个监听中设定了动画在结束后View的宽和高分别为match_parent和wrap_content。最后在ofItemViewHeight()方法中来具体操作动画,具体实现的代码又到了这一行:

final Animator animator = LayoutAnimator.ofHeight(holder.itemView, start, end); //具体的展开动画

3、所以继续上面的来到了LayoutAnimator这个类中,此类代码如下:

package com.jarchie.htgl.animation;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.view.View;
import android.view.ViewGroup;

public class LayoutAnimator {

    //监听动画的变化,不断设定view的高度值
    public static class LayoutHeightUpdateListener implements ValueAnimator.AnimatorUpdateListener {

        private final View mView;

        public LayoutHeightUpdateListener(View view) {
            mView = view;
        }

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            final ViewGroup.LayoutParams lp = mView.getLayoutParams();
            lp.height = (int) animation.getAnimatedValue();
            mView.setLayoutParams(lp);
        }
    }

    //真正实现具体展开动画的方法,使用ValueAnimator.ofInt生成一系列高度值,然后添加上面的监听
    public static Animator ofHeight(View view, int start, int end) {
        final ValueAnimator animator = ValueAnimator.ofInt(start, end);
        animator.addUpdateListener(new LayoutHeightUpdateListener(view));
        return animator;
    }

}

这个类中使用ValueAnimator.ofInt()生成一系列的高度值,然后通过监听动画的变化,不断设定View的高度值。

(三)实际使用——自定义Adapter

1、创建自定义的Adapter,继承自RecyclerView.Adapter,泛型传入自定义的ViewHolder,这个ViewHolder同样的继承自RecyclerView.ViewHolder,这里的ViewHolder需要实现我们工具类中的Expandable接口:

public class MyListAdapter extends RecyclerView.Adapter<MyListAdapter.ViewHolder>
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, Expandable

然后我们在我们的适配器类中去实例化KeepOneHolder这个类,在ViewHolder中创建了一个bind()方法,在这个方法中通过KeepOneHolder类的实例去调用它内部的bind()方法,然后在onBindViewHolder()方法中调用ViewHolder自定义的bind()方法:

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    holder.bind(position, mList.get(position));
}
public void bind(int pos, ApproveListBean.INFOBean bean) {
    keepOne.bind(this, pos);
}

这就是整个实现的过程,主要就是动画的三个工具类的编写,再次感谢这个Demo的原作者!

最后说一句:如果没有积分并且需要Demo源码的可以在留言中留下邮箱地址,我会发送给您!

更新:我在留言区看到很多小伙伴都需要此Demo,所以抽时间写了一个相对完整的Demo给需要的人,

项目地址:https://github.com/JArchie/ResizeRecyclerItemProject

发布了48 篇原创文章 · 获赞 47 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/JArchie520/article/details/79009017