欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-24 20:41:46
...

前言——项目说明

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

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

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















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

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

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

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

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

二、代码说明

(一)、布局文件说明

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

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

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

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源码的可以在留言中留下邮箱地址,我会发送给您!