Android---RecyclerView之动画(工具类)实现可展开列表
前言——项目说明
最近公司的项目需要实现一个列表的Item可展开收缩的效果,就是类似于QQ联系人中的那种效果。拿到这个需求之后,第一反应是用ExpandableListView,但是又想到RecyclerView这么强大,用它肯定也能实现,就想着可以定义父布局和子布局两种布局类型,再定义一个接口创建展开和隐藏两个监听方法,在展开时插入一条隐藏时删除一条,这样应该是可以实现的,但是这两种方案都有一个弊端,因为我们的后台接口把展示的所有信息都是放在同一个Bean里给我的,所以这样操作父类型和子类型的数据的时候还得自行分离,比较麻烦,所以我又在网上去寻找新的解决方案。
说来也巧,睡了一觉之后突然有了新的想法,Item中展开和收缩的两部分View还是作为一个整体定义在一个布局文件中,只要在点击的时候控制它显示隐藏就OK了啊!基于这个基本思路,借助强大的Google,最终找到了一种我觉得很简洁的解决方案,利用动画设置透明度来解决。感谢项目的原作者——Salomon BRYS,一个法国人!我是在*上面看到这个问题的,这里把地址提供大家:https://*.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源码的可以在留言中留下邮箱地址,我会发送给您!