android RecyclerView 粘带头部实现

本文详细讲解如何在Android中实现RecyclerView的粘带头部效果,通过分析ItemDecoration的工作原理,结合Fragment+RecyclerView+置顶View,揭示了利用ItemDecoration监听RecyclerView滚动事件来达成粘带效果的过程。并提醒开发者注意避免在onDraw中执行复杂操作,推荐使用悬浮层而非画笔绘制头部。文章提供了解决方案的工具类代码供读者参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于这方面的资料其实网上有不少,不过很多不懂原理的小白可能就一直会有个疑问,,到底粘带的动作是怎么完成的,本章是以Fragment+RecyclerView+置顶View完成的,不废话,直接代码解说

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


    <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:descendantFocusability="blocksDescendants" />

        <TextView
        android:id="@+id/tv_float_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#fafafa"
        android:paddingBottom="12dp"
        android:paddingLeft="11dp"
        android:paddingTop="12dp"
        android:text="--"
        android:textColor="#303030"
        android:textSize="14sp" />

</FrameLayout>

可以见到,布局很简单,其中tv_float_view 就是负责进行粘带悬浮的头部

要点1 RecyclerView.ItemDecoration

我们知道,要达到粘带的效果,那么我们就需要知道RecyclerView的滚动事件又或者item的移动事件,我们可以为RecyclerView添加一个ItemDecoration进行监听

为什么是它?

因为ItemDecoration加入到RecyclerView的时候
ItemDecoration.onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
会不断重绘你的布局,当然,你没有自定义的布局的话,也可以做其他事情,例如~~题目说的粘带头部.

要点2 ItemDecoration.onDraw的工作方式

当手指触碰RecyclerView的时候,这个onDraw会不断的读取手势(上下左右),从而一直调用onDraw,所以不建议onDraw中做些什么复杂的工作,这也就是为什么我们用悬浮层,而不是用画笔画头部.
那么,既然我们知道它是响应手势的事件,那就好说了,我们知道,控件的移动其实就是y轴的移动,而现在我要告诉你们,每当item移动的时候,参数parent里面的每一个子item的y轴坐标都被更新了,意思是,手指滑动的任何一个坐标系,每次都会被更新到item里.然后所谓的粘带就是让你的悬浮层的Y轴跟住某个item的y轴就可以完成粘带效果了,看到这里,我想大家应该知道怎么做了吧?

如果还不懂的话,童鞋就直接用我写的工具类代码吧

import android.graphics.Canvas;
import android.support.v7.widget.RecyclerView;
import android.view.View;

/**
 * Created by huangzongcheng
 * 固定头部粘带控件
 * 建议RecyclerView在FrameLayout布局内,将floatView置于FrameLayout布局最顶层,实现两个方法即可
 * BUG1:当头部高度大于非头部高度,会出现提早粘带
 * BUG2:当滑动太快的时候,因为粘带过程很快,会导致好像闪烁的效果
 */

public abstract class FloatHeadItemDecoraction extends RecyclerView.ItemDecoration {

    View root;

    /**
     * @param floatView 浮动视图布局
     */
    public FloatHeadItemDecoraction(View floatView) {
        this.root = floatView;
    }

    /**
     * 头部悬浮值发生改变后需要处理的事情
     *
     * @param postion 当前item索引
     */
    public abstract void onHeadChange(int postion);

    /**
     * 当前索引是否头部类型
     *
     * @param postion 当前item索引
     * @return
     */
    public abstract boolean isHeadType(int postion);

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        if (this.root == null) {
            return;
        }
        int beforY = 0;//上次移动的Y轴坐标
        int headHeight = 0;//head的高度
        int infoHeight = 0;//非head的高度
        int diffHeight = 0;//head&非head之间的高差
        for (int i = 0; i < parent.getChildCount(); i++) {
            View child = parent.getChildAt(i);
            int positon = parent.getChildAdapterPosition(child);
            //实际移动高度
            int currentY = child.getTop() + parent.getPaddingTop();

            //获得头部高度
            if (isHeadType(positon) && headHeight == 0) {
                headHeight = child.getMeasuredHeight();
            }
            //获得主体信息高度
            if (!isHeadType(positon) && infoHeight == 0) {
                infoHeight = child.getMeasuredHeight();
            }
            //获得头部和主体的高度差
            if (diffHeight == 0 && headHeight != 0 && infoHeight != 0) {
                diffHeight = Math.abs((headHeight - infoHeight));
            }
            //当第二个item是头部item,开始粘带动作
            if (i == 1 && isHeadType(positon)) {
                //如果当前item=1的头部移动坐标属于头部的高度范围内.允许进行粘带
                if (currentY >= 0 && currentY <= headHeight) {
                    /**
                     * 因为befortop的上次总移动距离=item的高度,而head和非hand的item之间是有高度差
                     * 所以悬浮窗的y轴要从两者的差距高度开始进行移动
                     */
                    int y = beforY + diffHeight;
                    root.setY(y);
                }
            } else if (i == 0) {//保持顶层悬浮
                root.setY(0);
            }
            //获得item=0的上次y轴坐标
            if (i == 0) {
                beforY = currentY;
                onHeadChange(positon);
            }
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值