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

如何在CoordinatorLayout中触发Toolbar的滚动行为

解决PDFView与CoordinatorLayout联动Toolbar滚动的问题

这个问题我之前也碰到过!因为PDFView本身并没有实现NestedScrollingChild接口,所以CoordinatorLayout自带的appbar_scrolling_view_behavior识别不到它的滚动行为,自然没法联动Toolbar。不过我们有两种靠谱的解决办法,看你喜欢哪种:

方案一:借助PDFView的ScrollListener手动触发联动

这种方式代码量少,快速见效,核心思路是监听PDFView的滚动事件,然后手动通知AppBarLayout的Behavior执行滚动动画。

步骤1:获取核心组件

在你的Activity/Fragment里,先拿到CoordinatorLayout、AppBarLayout和PDFView的实例:

val coordinatorLayout = findViewById<CoordinatorLayout>(R.id.coordinator_layout)
val appBarLayout = findViewById<AppBarLayout>(R.id.app_bar_layout)
val pdfView = findViewById<PDFView>(R.id.pdf_view)

步骤2:获取AppBarLayout的Behavior

从AppBarLayout的布局参数中取出对应的Behavior:

val appBarParams = appBarLayout.layoutParams as CoordinatorLayout.LayoutParams
val appBarBehavior = appBarParams.behavior as? AppBarLayout.Behavior

步骤3:给PDFView设置滚动监听

通过PDFView的OnScrollListener监听滚动方向和距离,然后调用Behavior的方法来驱动Toolbar的滚动:

pdfView.addOnScrollListener(object : PDFView.OnScrollListener {
    override fun onScrolled(scrollDx: Int, scrollDy: Int) {
        super.onScrolled(scrollDx, scrollDy)
        appBarBehavior?.let { behavior ->
            // 将PDFView的垂直滚动事件传递给AppBar的Behavior
            behavior.onNestedPreScroll(
                coordinatorLayout,
                appBarLayout,
                pdfView,
                0, // 水平滚动距离我们不需要,传0
                scrollDy, // 垂直滚动距离,正负对应滚动方向
                IntArray(2), // 消费的滚动距离,这里可以忽略
                0 // 滚动类型,默认传0即可
            )
        }
    }
})

记得确保你的Toolbar已经正确设置了app:layout_scrollFlags="scroll|enterAlways|snap",这样就能完美模拟出和NestedScrollView一样的Toolbar滚动效果了。

方案二:自定义PDFView实现NestedScrollingChild接口

这种方式更优雅,符合CoordinatorLayout的设计规范,自定义后的PDFView可以直接使用appbar_scrolling_view_behavior属性,不需要额外的监听代码。

自定义NestedScrollPDFView

创建一个继承自PDFView的类,实现NestedScrollingChild接口,借助NestedScrollingChildHelper来处理嵌套滚动逻辑:

class NestedScrollPDFView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : PDFView(context, attrs, defStyleAttr), NestedScrollingChild {

    private val nestedScrollHelper = NestedScrollingChildHelper(this)
    private var lastTouchY = 0

    init {
        // 开启嵌套滚动支持
        isNestedScrollingEnabled = true
    }

    override fun onTouchEvent(ev: MotionEvent): Boolean {
        val handled = super.onTouchEvent(ev)
        val currentY = ev.y.toInt()

        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                lastTouchY = currentY
                // 开始嵌套滚动,指定垂直方向
                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)
            }
            MotionEvent.ACTION_MOVE -> {
                val dy = lastTouchY - currentY
                // 先把滚动事件传递给父布局(CoordinatorLayout)处理
                if (dispatchNestedPreScroll(0, dy, null, null)) {
                    // 如果父布局消费了部分滚动,更新触摸位置
                    lastTouchY = currentY - dy
                }
                // 通知父布局剩余的滚动事件
                dispatchNestedScroll(0, dy, 0, 0, null)
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                // 停止嵌套滚动
                stopNestedScroll()
            }
        }
        return handled
    }

    // 实现NestedScrollingChild接口的必要方法
    override fun setNestedScrollingEnabled(enabled: Boolean) {
        nestedScrollHelper.isNestedScrollingEnabled = enabled
    }

    override fun isNestedScrollingEnabled(): Boolean {
        return nestedScrollHelper.isNestedScrollingEnabled
    }

    override fun startNestedScroll(axes: Int): Boolean {
        return nestedScrollHelper.startNestedScroll(axes)
    }

    override fun stopNestedScroll() {
        nestedScrollHelper.stopNestedScroll()
    }

    override fun hasNestedScrollingParent(): Boolean {
        return nestedScrollHelper.hasNestedScrollingParent()
    }

    override fun dispatchNestedScroll(
        dxConsumed: Int,
        dyConsumed: Int,
        dxUnconsumed: Int,
        dyUnconsumed: Int,
        offsetInWindow: IntArray?
    ): Boolean {
        return nestedScrollHelper.dispatchNestedScroll(
            dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow
        )
    }

    override fun dispatchNestedPreScroll(
        dx: Int,
        dy: Int,
        consumed: IntArray?,
        offsetInWindow: IntArray?
    ): Boolean {
        return nestedScrollHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)
    }
}

在布局中使用自定义View

把原来的PDFView替换成自定义的NestedScrollPDFView,然后加上app:layout_behavior="@string/appbar_scrolling_view_behavior"属性:

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

这样设置后,你的PDFView就和NestedScrollView一样,能自动联动Toolbar的滚动隐藏/显示了。

两种方案对比

  • 方案一:代码量少,快速实现,适合临时需求或者不想自定义View的场景。
  • 方案二:更符合Android的嵌套滚动设计,扩展性更好,适合长期维护的项目。

内容的提问来源于stack exchange,提问作者Michel Feinstein

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:16:40