RecyclerView item 可展开动画效果的实现

雨燕双飞 提交于 2019-12-04 19:22:40

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/android_freshman/article/details/94354088
RecyclerView item 可展开动画效果的实现
前文提要:
1.相关说明:
1-1.布局文件:
1-2.动画工具类说明(代码我基本上都添加了注释):
1-3.问题:
2.如何使用:
2-1.viewHoler 需要实现 ExpandableViewHoldersUtil.Expandable 接口
2-2.adapter
2-3.ExpandableViewHoldersUtil
3.结束:
前文提要:
Android list 列表里面空间的显示和 隐藏,基本都是用的View.VISIBLE 和 View.GONE 实现的,展示的效果有点突兀,看了ios 同事做的相同的效果,他们的很顺畅,所以决定做一个相同的效果.

已经上传到github 上面地址是 demo的项目地址 :https://github.com/luhui2014/ExpandableViewHolder/tree/master

1.相关说明:
参考资料:Android—RecyclerView之动画(工具类)实现可展开列表

1-1.布局文件:
将需要展开收缩的那部分布局的透明度在xml文件里默认设置为0,在代码中设置一样


1-2.动画工具类说明(代码我基本上都添加了注释):
这里我就不赘述了,请参考原文 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;


    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
还有一段比较有意思的地方是 处理了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);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1-3.问题:
但是原作里没有处理好展开和收缩缓存的问题,已经解耦的问题。对应展开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);
                    }
                }
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
开放出来一个回调接口处理:holder.doCustomAnim(true);,根据需要自行增加相关的动画;

对于记录展开和收缩状态的问题,定义了一个全局的变量用于存储,初始化的时候,进行判断。在用户点击动画的时候,进行相应的增加和删除处理:


这里用String 记录是为了处理删除的时候数据越界的问题:

private void deletePositionInExpaned(int pos) {
        //remove Object 直接写int,会变成index,造成数组越界
        explanedList.remove(pos + "");
    }
1
2
3
4

源码中有这么一段,直接删除int 会存在数值越界的问题;

2.如何使用:
2-1.viewHoler 需要实现 ExpandableViewHoldersUtil.Expandable 接口
回调的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);
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2-2.adapter
在 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2-3.ExpandableViewHoldersUtil
1.setNeedExplanedOnlyOne
//true 点击第二个会收缩前一个
//false 不会
ExpandableViewHoldersUtil.getInstance().init().setNeedExplanedOnlyOne(false);

2.//清空记录展开还是关闭的缓存数据,这个每次在下拉刷新的时候,是否清空根据需求自行处理
ExpandableViewHoldersUtil.getInstance().resetExpanedList();

3.结束:
参照的相关的代码并根据自己在使用过程中遇到的问题,做了相关处理,原文链接已贴在开始。特意写了一个简单demo 上传到git上,

还有问题请参考具体的实现demo :https://github.com/luhui2014/ExpandableViewHolder/tree/master
————————————————
版权声明:本文为CSDN博主「android_小路」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/android_freshman/article/details/94354088

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!