自定义CoordinatorLayout Behavior如何响应NestedScrollView滚动事件?
这个问题我之前也遇到过!核心原因是CoordinatorLayout的layoutDependsOn只能识别直接同级的View作为依赖,你的NestedScrollView藏在ViewPager的Fragment里面,属于深层子View,所以Behavior根本检测不到它,只会把同级的ViewPager当成依赖对象。
给你两种靠谱的解决方案,推荐第一种,更符合CoordinatorLayout的设计逻辑:
方案一:利用CoordinatorLayout的嵌套滚动Behavior直接监听滚动
修改你的NavigationViewBehavior,放弃依赖NestedScrollView,转而通过嵌套滚动回调来接收所有子层级的滚动事件:
package ui.behavior import android.content.Context import android.support.design.widget.CoordinatorLayout import android.support.v4.widget.NestedScrollView import android.util.AttributeSet import android.util.TypedValue import android.view.View import ui.home.navigation.NavigationView class NavigationViewBehavior( context: Context, attributeSet: AttributeSet ) : CoordinatorLayout.Behavior<NavigationView>(context, attributeSet) { private var originalHeight = 0 private val minHeight: Int by lazy { // 定义导航栏的最小高度,这里设为48dp,可按需修改 TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 48f, context.resources.displayMetrics ).toInt() } override fun onLayoutChild(parent: CoordinatorLayout, child: NavigationView, layoutDirection: Int): Boolean { // 保存导航栏的原始高度,用于后续恢复 originalHeight = child.height return super.onLayoutChild(parent, child, layoutDirection) } override fun onStartNestedScroll( coordinatorLayout: CoordinatorLayout, child: NavigationView, directTargetChild: View, target: View, axes: Int, type: Int ): Boolean { // 只关心垂直方向的滚动,且滚动源是NestedScrollView return axes == View.SCROLL_AXIS_VERTICAL && target is NestedScrollView } override fun onNestedScroll( coordinatorLayout: CoordinatorLayout, child: NavigationView, target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int ) { super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type) // dyConsumed:已被消耗的滚动距离,正数=向上滚,负数=向下滚 val newHeight = child.height - dyConsumed // 限制高度在最小高度和原始高度之间 val finalHeight = newHeight.coerceIn(minHeight, originalHeight) child.layoutParams.height = finalHeight child.requestLayout() } }
然后确保你的布局不变,NavigationView已经绑定了这个Behavior,不需要额外修改ViewPager的属性。
方案二:在Fragment中监听NestedScrollView,通过接口回调通知Activity调整高度
如果第一种方案不符合你的需求,也可以用这种更直接的方式:
- 在Fragment中定义滚动回调接口:
interface ScrollCallback { fun onVerticalScroll(dy: Int) } class YourFragment : Fragment() { private var scrollCallback: ScrollCallback? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val nestedScrollView = view.findViewById<NestedScrollView>(R.id.your_nested_scroll_id) nestedScrollView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> val dy = scrollY - oldScrollY scrollCallback?.onVerticalScroll(dy) } } fun setScrollCallback(callback: ScrollCallback) { this.scrollCallback = callback } }
- 在Activity中实现接口,调整NavigationView高度:
class MainActivity : AppCompatActivity(), ScrollCallback { private lateinit var navigationView: NavigationView private var originalNavHeight = 0 private val minNavHeight = 48.dpToPx() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.your_layout) navigationView = findViewById(R.id.navigation_view) originalNavHeight = navigationView.height // 给Fragment设置回调 val fragment = supportFragmentManager.findFragmentById(R.id.viewpager)?.getChildFragmentManager()?.fragments?.get(0) as? YourFragment fragment?.setScrollCallback(this) } override fun onVerticalScroll(dy: Int) { val newHeight = navigationView.height - dy val finalHeight = newHeight.coerceIn(minNavHeight, originalNavHeight) navigationView.layoutParams.height = finalHeight navigationView.requestLayout() } // 扩展函数:dp转px private fun Int.dpToPx(): Int { return TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), resources.displayMetrics ).toInt() } }
关键注意点
- 方案一的优势是完全基于CoordinatorLayout的原生机制,耦合度低,不需要Fragment和Activity之间做额外通信;
- 确保你的NestedScrollView设置了
android:fillViewport="true",否则可能出现滚动异常; - 可以根据需求调整
minHeight的值,或者添加平滑动画让高度变化更自然(比如用ValueAnimator来过渡高度)。
内容的提问来源于stack exchange,提问作者Robby Smet
相关产品推荐
相关产品推荐

