Recyclerview 实现Item分组悬浮效果

本demo的使用限制:悬浮view的高度需要小于等于item的高,否则需要修改滑动逻辑。

目前Recyclerview实现item悬浮效果主流的有两种:
1、使用ItemDecoration来实现。
2、设置Recyclerview的addOnScrollListener来实现。
这里我们使用的是第二种方法来实现,个人感觉更简单点。

设置Activity的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ScrollActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/my_rv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <TextView
        android:id="@+id/suspend_tv"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:paddingRight="20dp"
        android:paddingLeft="20dp"
        android:gravity="center_vertical"
        android:background="@color/colorAccent"
        android:textSize="14sp"
        android:text=""/>
</RelativeLayout>

设置item的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:orientation="vertical">

    <TextView
        android:id="@+id/item_suspend_tv"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:paddingStart="20dp"
        android:paddingEnd="20dp"
        android:gravity="center_vertical"
        android:background="@color/colorAccent"
        android:textSize="14sp"
        android:text=""/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:paddingStart="20dp"
        android:paddingEnd="20dp"
        android:gravity="center_vertical"
        android:orientation="horizontal">
        <TextView
            android:id="@+id/item_time_tv"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text=""/>
        <TextView
            android:id="@+id/item_count_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=""/>
    </LinearLayout>

</LinearLayout>

此处悬浮view(也就是suspend_tv)的高需要和item顶部分组view(也就是item_suspend_tv)的高设置一致。

 

因为只有每组数据中的第一个数据需要在item中显示分组信息,所以在adapter的onBindViewHolder中判断该item是否需要显示分组信息。

    @Override
    public void onBindViewHolder(@NonNull ScrollHolder holder, int position) {
        MyDataBean myDataBean = dataBeans.get(position);
        holder.itemSuspendTv.setText(myDataBean.time);
        holder.itemTimeTv.setText(myDataBean.time);
        holder.itemCountTv.setText(myDataBean.getCount() + "");
        holder.itemSuspendTv.setVisibility(View.GONE);
        if (position == 0) {
            holder.itemSuspendTv.setVisibility(View.VISIBLE);
        } else if (position > 0 && position < getItemCount() - 1) {
            MyDataBean tWalletBean = dataBeans.get(position - 1);
            if (tWalletBean.getTime().equals(myDataBean.getTime())) {
                holder.itemSuspendTv.setVisibility(View.GONE);
            } else {
                holder.itemSuspendTv.setVisibility(View.VISIBLE);
            }
        }
    }

在activity中正常初始化adapter,
给RecyclerView设置addOnScrollListener(核心代码):

myRv.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                // 获取悬浮View的高
                suspendBarHeight = suspendTv.getHeight();
            }

            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                Log.i(TAG, "dy:" + dy);
                if (datas.size() <= 1) {
                    return;
                }

                int firstItemPosition = llm.findFirstVisibleItemPosition();
                //获取最顶部的item
                View firstView = llm.findViewByPosition(firstItemPosition);
                MyDataBean firstBean = datas.get(firstItemPosition);

                int secondItemPosition = llm.findFirstVisibleItemPosition() + 1;
                // 获取第二个显示的item
                View secondView = llm.findViewByPosition(secondItemPosition);
                MyDataBean secondBean = datas.get(secondItemPosition);

                if (dy > 0) { // 上滑
                    // 设置dy < 200是因为当向上滑动过快时,悬浮的view会被挤上去,
                    if (dy < 200) {
                        if (secondBean.isSuspend()) {
                             /*
                             * 如果当屏幕上显示的第二个item为要悬浮的内容,并且secondViewY < suspendBarHeight,
                             * 也就是第二个item的顶部到达悬浮view的底部时,此时第二个item开始向上推动悬浮的view()
                             */
                            float secondViewY = secondView.getY();
                            if (secondViewY < suspendBarHeight) {
                                float setY = secondViewY - suspendBarHeight;
                                suspendTv.setY(setY);
                            }
                        }
                    } else {
                        // dy >= 200时,悬浮view在顶端全部显示,
                        suspendTv.setY(0);
                    }

                     /*
                     * 当显示的第一个item的数据是要悬浮的类型,并且getY <= 0,也就是有一部分被划出屏幕,
                     * 此时直接显示悬浮的view,遮盖住第一个item的顶部,视觉上替代item的顶部分组部分,
                     * 其实此时item顶部分组部分的view已被划出屏幕
                     */
                    if (firstBean.isSuspend()) {
                        if (null != firstView && firstView.getY() <= 0) {
                            suspendTv.setY(0);
                        }
                    }

                } else { // 下拉
                     /*
                     * 如果第二个可见item是需要悬浮的数据,为什么是第二个item?因为第一个item从屏幕上面刚划出来时,
                     * 会被悬浮的view先遮挡住,
                     */
                    if (secondBean.isSuspend()) {
                        float secondViewY = secondView.getY();
                        /*
                        * 当第二个可见item的getY小于悬浮view的高suspendBarHeight时,动态设置悬浮view的setY值,
                        * 此时屏幕顶部悬浮view的位置会被瞬间移到屏幕外,一点点被拉到屏幕内,
                        * 呈现出悬浮view从屏幕顶部被拉出来的视觉,此时我们看到的悬浮view其实是item顶部分组的view,
                        * */
                        if (secondViewY < suspendBarHeight) {
                            suspendTv.setY(secondViewY - suspendBarHeight);
                        } else {
                            suspendTv.setY(0);
                        }
                    } else {
                        suspendTv.setY(0);
                    }
                }
                suspendTv.setText(firstBean.time);
            }
        });

通过addOnScrollListener设置悬浮效果会有一个问题,那就是当向上滑动过快的时候,悬浮的view有时会被顶到屏幕外面,所以在这里设置了dy < 200来避免。

 

 

 

 

 

 

 

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值