Android list 列表里面空间的显示和 隐藏,基本都是用的View.VISIBLE 和 View.GONE 实现的,展示的效果有点突兀,看了ios 同事做的相同的效果,他们的很顺畅,所以决定做一个相同的效果.
已经上传到github 上面地址是 demo的项目地址 :https://github.com/luhui2014/ExpandableViewHolder/tree/master
参考资料:Android—RecyclerView之动画(工具类)实现可展开列表
将需要展开收缩的那部分布局的透明度在xml文件里默认设置为0,在代码中设置一样
这里我就不赘述了,请参考原文 Android—RecyclerView之动画(工具类)实现可展开列表
相关原理就是:利用属性动画,动态计算view展开后的高度,实现动画效果。中间插了一段alpha 的动画,为了过渡显示,关键代码:
//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;
}
还有一段比较有意思的地方是 处理了recyclerView 的的回收,增加了一个动画监听,在动画结束的时候,让recyclerView 自身去处理是否回收的问题
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);
}
}
但是原作里没有处理好展开和收缩缓存的问题,已经解耦的问题。对应展开item之后是否需要其他的动画,这里应该开放出来,自行去实现,所以我这里就改了一下:
/**
* 响应ViewHolder的点击事件
*
* @param holder holder对象
*/
@SuppressWarnings("unchecked")
public void toggle(VH holder) {
int position = holder.getPosition();
if (explanedList.contains(position + "")) {
opened = -1;
deletePositionInExpaned(position);
holder.doCustomAnim(true);
ExpandableViewHoldersUtil.getInstance().closeHolder(holder, holder.getExpandView(), true);
} else {
preOpen = opened;
opened = position;
addPositionInExpaned(position);
holder.doCustomAnim(false);
ExpandableViewHoldersUtil.getInstance().openHolder(holder, holder.getExpandView(), true);
//是否要关闭上一个
if (needExplanedOnlyOne && preOpen != position) {
final VH oldHolder = (VH) ((RecyclerView) holder.itemView.getParent()).findViewHolderForPosition(preOpen);
if (oldHolder != null) {
Log.e("KeepOneHolder", "oldHolder != null");
ExpandableViewHoldersUtil.getInstance().closeHolder(oldHolder, oldHolder.getExpandView(), true);
deletePositionInExpaned(preOpen);
}
}
}
}
}
开放出来一个回调接口处理:holder.doCustomAnim(true);,根据需要自行增加相关的动画;
对于记录展开和收缩状态的问题,定义了一个全局的变量用于存储,初始化的时候,进行判断。在用户点击动画的时候,进行相应的增加和删除处理:
这里用String 记录是为了处理删除的时候数据越界的问题:
private void deletePositionInExpaned(int pos) {
//remove Object 直接写int,会变成index,造成数组越界
explanedList.remove(pos + "");
}
回调的view,就是处理展开动画的view,
不要忘记初始化 keepOne = ExpandableViewHoldersUtil.getInstance().getKeepOneHolder();
class ViewHolder extends RecyclerView.ViewHolder implements ExpandableViewHoldersUtil.Expandable {
TextView tvTitle;
ImageView arrowImage;
LinearLayout lvArrorwBtn;
LinearLayout lvLinearlayout;
public ViewHolder(@NonNull View itemView) {
super(itemView);
tvTitle = itemView.findViewById(R.id.item_user_concern_title);
lvLinearlayout = itemView.findViewById(R.id.item_user_concern_link_layout);
lvArrorwBtn = itemView.findViewById(R.id.item_user_concern_arrow);
arrowImage = itemView.findViewById(R.id.item_user_concern_arrow_image);
keepOne = ExpandableViewHoldersUtil.getInstance().getKeepOneHolder();
lvLinearlayout.setVisibility(View.GONE);
lvLinearlayout.setAlpha(0);
}
@Override
public View getExpandView() {
return lvLinearlayout;
}
@Override
public void doCustomAnim(boolean isOpen) {
if (isOpen) {
ExpandableViewHoldersUtil.getInstance().rotateExpandIcon(arrowImage, 180, 0);
} else {
ExpandableViewHoldersUtil.getInstance().rotateExpandIcon(arrowImage, 0, 180);
}
}
}
在 onBindViewHolder 的时候需要绑定对应的view,初始展开和收缩的状态,自然点击效果就是 keepOne.toggle(viewHolder);
@Override
public void onBindViewHolder(@NonNull final ViewHolder viewHolder, int position) {
viewHolder.tvTitle.setText("中美经贸磋商 po=" + position);
keepOne.bind(viewHolder, position);
viewHolder.tvTitle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
keepOne.toggle(viewHolder);
}
});
viewHolder.lvArrorwBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
keepOne.toggle(viewHolder);
}
});
}
1.setNeedExplanedOnlyOne
//true 点击第二个会收缩前一个
//false 不会
ExpandableViewHoldersUtil.getInstance().init().setNeedExplanedOnlyOne(false);
2.//清空记录展开还是关闭的缓存数据,这个每次在下拉刷新的时候,是否清空根据需求自行处理
ExpandableViewHoldersUtil.getInstance().resetExpanedList();
参照的相关的代码并根据自己在使用过程中遇到的问题,做了相关处理,原文链接已贴在开始。特意写了一个简单demo 上传到git上,
还有问题请参考具体的实现demo :https://github.com/luhui2014/ExpandableViewHolder/tree/master