Kotlin Fragment滑动手势导航及ViewPager2 currentItem失效问题
Fragment内实现滑动手势触发导航跳转
在单个Fragment中接入手势检测实现滑动跳转,按以下步骤实现即可:
- 先在Fragment类中声明
GestureDetector实例,提前定义好滑动距离、滑动速度阈值,避免滑动误触。阈值建议用dp值转成像素值适配不同分辨率设备。 - 在
onViewCreated生命周期中初始化手势检测器,重写onFling方法判断滑动方向,匹配到目标滑动方向后直接调用findNavController().navigate()执行跳转。 - 给Fragment根视图设置触摸事件监听,把收到的触摸事件全部传给手势检测器处理。
核心实现代码:
class DemoFragment : Fragment() { private lateinit var gestureDetector: GestureDetector private val swipeThreshold = 100.dpToPx() private val swipeVelocityThreshold = 100.dpToPx() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_demo, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) gestureDetector = GestureDetector(requireContext(), object : GestureDetector.SimpleOnGestureListener() { override fun onFling( e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float ): Boolean { if (e1 == null || Math.abs(e2.y - e1.y) > swipeThreshold) return false // 匹配左到右滑动 if (e2.x - e1.x > swipeThreshold && Math.abs(velocityX) > swipeVelocityThreshold) { // 替换为自身navigation graph中配置的action id findNavController().navigate(R.id.action_demoFragment_to_targetFragment) return true } return false } }) view.setOnTouchListener { v, event -> gestureDetector.onTouchEvent(event) v.performClick() true } } private fun Int.dpToPx(): Int = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics ).toInt() }
注意事项:如果Fragment布局中包含RecyclerView、ScrollView等可滚动子控件,需要额外处理触摸事件冲突,避免滚动列表时误触发跳转。不要在
onCreateView阶段执行视图绑定、手势初始化操作,避免空指针。
ViewPager2子Fragment调用currentItem失效问题
问题原因
子Fragment中调用currentItem返回null、无响应,核心原因是调用方没有获取到宿主Fragment绑定的ViewPager2实例:ViewPager2定义在宿主Fragment的布局中,子Fragment直接在自身视图内findView自然无法拿到实例,操作null对象自然不会有页面切换效果。
解决方案
优先使用官方推荐的Fragment Result API实现通信,无耦合、不存在生命周期问题:
- 第一步:在宿主Fragment
dayspager22.kt中注册结果监听,收到子Fragment的切换请求时操作ViewPager2切换页面:
class DaysPager22Fragment : Fragment() { private lateinit var viewPager: ViewPager2 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewPager = view.findViewById(R.id.view_pager) viewPager.adapter = ViewPagerAdapter(this) // 注册页面切换请求监听 childFragmentManager.setFragmentResultListener("switch_page", this) { _, bundle -> val targetIndex = bundle.getInt("target_index", 0) viewPager.currentItem = targetIndex } } }
- 第二步:在子Fragment(days_1.kt、days_2.kt、days_3.kt)需要触发页面切换的位置,直接发送结果即可,无需持有ViewPager2引用:
// 例:days_1中跳转到days_2(索引从0开始,days_1对应0、days_2对应1、days_3对应2) childFragmentManager.setFragmentResult("switch_page", bundleOf("target_index" to 1))
如果项目结构简单不想用事件通信,也可以直接在子Fragment中获取父Fragment实例,再通过父Fragment的视图查找ViewPager2:
// 子Fragment中调用 val hostFragment = parentFragment as? DaysPager22Fragment hostFragment?.view?.findViewById<ViewPager2>(R.id.view_pager)?.currentItem = 1
注意事项:不要在子Fragment的
onCreate、onCreateView生命周期中触发页面切换,此时宿主的ViewPager2还未完成初始化,会出现空指针。传入的target_index不能超出ViewPagerAdapter的页面总数,否则会触发索引越界崩溃。
内容的提问来源于stack exchange,提问作者jung ho jang
相关产品推荐
相关产品推荐

