Realization of RecyclerView item expandable animation effect

Preliminary summary:

The display and hiding of the space in the Android list list are basically achieved with View.VISIBLE and View.GONE. The display effect is a bit abrupt. After seeing the same effect done by ios colleagues, they are very smooth, so I decided to make one The same effect.

Insert picture description here

It has been uploaded to github. The above address is the demo project address: https://github.com/luhui2014/ExpandableViewHolder/tree/master

1. Related instructions:

Reference: Android—RecyclerView's animation (tool class) implements expandable list

1-1. Layout file:

Set the transparency of the part of the layout that needs to be expanded and contracted to 0 by default in the xml file, and set the same in the code
Insert picture description here

1-2. Animation tool class description (I basically added comments to the code):

I won’t go into details here, please refer to the original Android-RecyclerView animation (tool class) to implement an expandable list

The related principle is: use attribute animation to dynamically calculate the height of the expanded view to achieve animation effects. An alpha animation is inserted in the middle, in order to transition display, the key code:

//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;


    }

Another interesting part is that it handles the recycling of recyclerView and adds an animation monitor. When the animation ends, let the recyclerView itself handle the issue of whether to recycle.

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-3. Question:

But the original work did not deal with the problem of expanding and shrinking the cache, which has been decoupled. Corresponding to whether other animations are needed after expanding the item, here should be opened up and implemented by yourself, so I changed it here:

  /**
         * 响应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);
                    }
                }
            }
        }
    }

Open up a callback interface processing: holder.doCustomAnim(true);, add related animations as needed;

For the problem of recording the expansion and contraction state, a global variable is defined for storage, and judgment is made during initialization. When the user clicks on the animation, the corresponding processing of adding and deleting is performed:

Insert picture description here
String record is used here to deal with the problem of data out of bounds when deleting:

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

Insert picture description here
There is such a paragraph in the source code, if you delete int directly, there will be a problem that the value is out of range;

2. How to use:

2-1. viewHoler needs to implement ExpandableViewHoldersUtil.Expandable interface

The callback view is the view that handles the expansion animation.

Don't forget to initialize 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);
            }
        }
    }

2-2.adapter

When onBindViewHolder needs to bind the corresponding view, the initial expansion and contraction state, the natural click effect is 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);
                }
            });
        }

2-3.ExpandableViewHoldersUtil

1.setNeedExplanedOnlyOne
//true Clicking on the second one will shrink the previous one
//false will not
ExpandableViewHoldersUtil.getInstance().init().setNeedExplanedOnlyOne(false);

2.//Clear the cached data of whether the record is expanded or closed. Each time this is pulled down to refresh, whether to clear
ExpandableViewHoldersUtil.getInstance().resetExpanedList();

3. End:

I refer to the relevant code and deal with the problems I encountered in the process. The original link has been posted at the beginning. I specially wrote a simple demo and uploaded it to git,

If you have any questions, please refer to the specific implementation demo: https://github.com/luhui2014/ExpandableViewHolder/tree/master

Guess you like

Origin blog.csdn.net/android_freshman/article/details/94354088