You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何检测NestedScrollView滚动到底部且用户正在执行下拉操作?

解决NestedScrollView到底部后下拉动作的检测方案

我之前也碰到过一模一样的需求!既要保留AppBarLayout和NestedScrollView的联动效果(就是你说的appbar_scrolling_view_behavior正常工作),又要精准判断用户滚动到底部后的下拉动作,确实踩了不少坑,分享下我最终用的方案:

核心思路

不需要重写CoordinatorLayout.Behavior,只需要自定义NestedScrollView,通过监听它的滚动状态、滚动方向以及是否到底部的条件组合,就能精准触发我们要的回调。这样完全不会破坏原有Behavior的联动效果。

具体实现代码

1. 自定义NestedScrollView类

这个类会帮我们监听滚动细节,精准判断到底部+下拉的动作:

class CustomNestedScrollView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : NestedScrollView(context, attrs, defStyleAttr) {

    private var scrollListener: OnBottomPullListener? = null
    private var lastScrollY = 0

    // 定义回调接口,用于通知外部到底部下拉的事件
    interface OnBottomPullListener {
        fun onBottomPulled()
    }

    // 设置监听器的方法
    fun setOnBottomPullListener(listener: OnBottomPullListener) {
        this.scrollListener = listener
    }

    // 监听滚动位置变化,基础判断逻辑
    override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
        super.onScrollChanged(l, t, oldl, oldt)
        // 判断是否滚动到底部:canScrollVertically(1)返回false表示无法再向下滚动
        val isAtBottom = !canScrollVertically(1)
        // 判断是否是下拉动作:当前滚动位置t > 上一次位置lastScrollY
        val isPullingDown = t > lastScrollY

        if (isAtBottom && isPullingDown) {
            scrollListener?.onBottomPulled()
        }
        lastScrollY = t
    }

    // 结合嵌套滚动类型,过滤惯性滚动,只响应用户主动触摸下拉
    override fun onNestedScroll(target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int) {
        super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type)
        // TYPE_TOUCH表示是用户主动触摸滚动,排除惯性滚动的误触发
        if (type == ViewCompat.TYPE_TOUCH) {
            val isAtBottom = !canScrollVertically(1)
            // dyUnconsumed < 0:表示用户向下拉的动作没有被ScrollView消耗(已经到底了)
            val isPullingDown = dyUnconsumed < 0
            if (isAtBottom && isPullingDown) {
                scrollListener?.onBottomPulled()
            }
        }
    }
}

2. 布局中使用自定义ScrollView

完全保留原来的appbar_scrolling_view_behavior,只把原生NestedScrollView替换成我们自定义的:

<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <!-- 你的Toolbar、CollapsingToolbarLayout等内容 -->
    </com.google.android.material.appbar.AppBarLayout>

    <com.yourpackage.CustomNestedScrollView
        android:id="@+id/customNestedScrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <!-- 你的滚动内容,比如LinearLayout、RecyclerView等 -->
    </com.yourpackage.CustomNestedScrollView>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

3. 在Activity/Fragment中设置回调

customNestedScrollView.setOnBottomPullListener {
    // 这里就是你要处理的"到底部且下拉"的逻辑
    Toast.makeText(this@MainActivity, "已滚动到底部,正在下拉!", Toast.LENGTH_SHORT).show()
}

关键细节说明

  • canScrollVertically(1):这是Android官方推荐的判断是否能向下滚动的方法,返回false就说明已经到了ScrollView的底部。
  • type == ViewCompat.TYPE_TOUCH:过滤掉惯性滚动的情况,确保只响应用户主动的触摸下拉动作,避免误触发回调。
  • dyUnconsumed < 0:当ScrollView已经到底时,用户继续下拉的动作不会被ScrollView消耗,此时dyUnconsumed会是负数,以此精准判断用户正在执行下拉操作。

内容的提问来源于stack exchange,提问作者Jocky Doe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 07:31:10