ViewPager2调用setCurrentItem时切换动画中途卡顿问题求助
Hey,针对你遇到的ViewPager2调用setCurrentItem时偶尔卡顿的问题,结合你给出的警告日志,我帮你梳理下核心原因、解决办法,顺便给些代码质量和性能优化的建议:
一、卡顿根源定位
从你提到的警告日志来看,卡顿主要来自两个核心点:
- MediaPlayer资源未及时回收:切换页面时MediaPlayer的清理动作滞后,导致动画过程中还有后台IO/线程操作抢占资源
- 布局重绘冲突:ViewPager2底层依赖的RecyclerView,和页面里的AppCompatTextView频繁触发
requestLayout,在动画过程中引发重复布局计算,打断了动画流畅性
二、针对性修复方案
1. 精准管控Fragment的媒体资源
ViewPager2会预加载相邻页面,所以不能只依赖Fragment的常规生命周期,要结合页面可见性和ViewPager2的页面回调来控制MediaPlayer的状态:
// 在PresentationSlideFragment中 override fun onPause() { super.onPause() // 区分是暂时切走还是即将销毁,提前释放资源 if (!userVisibleHint || isRemoving) { videoView.stopPlayback() // 手动清理VideoView内部的MediaPlayer实例,避免内存泄漏 try { val mediaPlayerField = VideoView::class.java.getDeclaredField("mMediaPlayer") mediaPlayerField.isAccessible = true (mediaPlayerField.get(videoView) as? MediaPlayer)?.release() mediaPlayerField.set(videoView, null) } catch (e: Exception) { e.printStackTrace() } } } override fun onResume() { super.onResume() // 只有当前页面可见且未播放时,才恢复播放 if (userVisibleHint && !videoView.isPlaying) { videoView.start() } }
然后在PresentationActivity的ViewPager2回调里,确保页面切换时只保留当前页面的媒体资源:
viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { super.onPageSelected(position) // 先暂停所有非当前页面的视频播放 supportFragmentManager.fragments.filterIsInstance<PresentationSlideFragment>() .forEach { fragment -> val fragmentTag = "f$position" // 对应FragmentStateAdapter的默认Tag规则 if (fragment.tag != fragmentTag) { fragment.videoView.stopPlayback() } } // 恢复当前页面的播放(如果之前有进度) (supportFragmentManager.findFragmentByTag("f$position") as? PresentationSlideFragment)?.apply { if (videoView.currentPosition > 0) { videoView.start() } } } })
小贴士:别等到
onDestroyView才释放资源,因为ViewPager2会缓存Fragment,onDestroyView只有在页面被回收时才触发,提前处理能避免动画时的资源竞争。
2. 解决布局重绘警告
针对RecyclerView和AppCompatTextView的requestLayout警告,从布局和代码两方面优化:
- 布局文件调整:给Fragment根布局添加
android:descendantFocusability="blocksDescendants",避免TextView获取焦点触发重绘;同时给VideoView设置固定占比,不要用wrap_content减少布局计算:
<!-- PresentationSlideFragment的布局文件 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:descendantFocusability="blocksDescendants"> <VideoView android:id="@+id/videoView" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <AppCompatTextView android:id="@+id/contentTv" android:layout_width="match_parent" android:layout_height="wrap_content" android:focusable="false" android:focusableInTouchMode="false" /> </LinearLayout>
- 代码层面避免频繁触发重绘:如果TextView内容是动态更新的,缓存内容值,只有当内容真正变化时再调用
setText;绝对不要在动画过程中调用requestLayout相关方法。
3. 优化ViewPager2跳转逻辑
调用setCurrentItem时,可适当调整动画配置,减少动画过程中的资源压力:
// 跳转页面时,可自定义动画时长(默认时长偏长,容易暴露卡顿) viewPager2.setCurrentItem(targetPosition, true) val recyclerView = viewPager2.getChildAt(0) as RecyclerView recyclerView.itemAnimator?.apply { addDuration = 150 removeDuration = 150 moveDuration = 150 changeDuration = 150 }
三、代码质量与性能优化建议
- 替换为官方FragmentStateAdapter:如果你的
ViewPagerFragmentAdapter是自定义的,建议换成官方的FragmentStateAdapter,它针对ViewPager2做了生命周期和缓存优化,能减少Fragment管理的bug:
class PresentationSlideAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fragmentManager, lifecycle) { private val slideList = mutableListOf<SlideData>() fun updateData(data: List<SlideData>) { slideList.clear() slideList.addAll(data) notifyDataSetChanged() } override fun getItemCount() = slideList.size override fun createFragment(position: Int) = PresentationSlideFragment.newInstance(slideList[position]) }
- 替换VideoView为ExoPlayer:ExoPlayer比原生VideoView有更好的缓存、线程管理和错误处理,能大幅减少媒体相关的卡顿和内存泄漏问题。
- 控制预加载数量:通过
viewPager2.offscreenPageLimit = 1设置预加载页面数,不要设太大,减少同时存活的Fragment数量,降低内存占用。 - 避免主线程阻塞:所有媒体资源的加载、释放操作都放到子线程处理,绝对不要在主线程做IO操作。
- 内存泄漏检测:用LeakCanary检测MediaPlayer和Fragment的内存泄漏,确保资源被彻底释放。
内容的提问来源于stack exchange,提问作者Sumit
相关产品推荐
相关产品推荐

