如何在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

