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

ViewPager2调用setCurrentItem时切换动画中途卡顿问题求助

Hey,针对你遇到的ViewPager2调用setCurrentItem时偶尔卡顿的问题,结合你给出的警告日志,我帮你梳理下核心原因、解决办法,顺便给些代码质量和性能优化的建议:

一、卡顿根源定位

从你提到的警告日志来看,卡顿主要来自两个核心点:

  1. MediaPlayer资源未及时回收:切换页面时MediaPlayer的清理动作滞后,导致动画过程中还有后台IO/线程操作抢占资源
  2. 布局重绘冲突: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
}
三、代码质量与性能优化建议
  1. 替换为官方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])
}
  1. 替换VideoView为ExoPlayer:ExoPlayer比原生VideoView有更好的缓存、线程管理和错误处理,能大幅减少媒体相关的卡顿和内存泄漏问题。
  2. 控制预加载数量:通过viewPager2.offscreenPageLimit = 1设置预加载页面数,不要设太大,减少同时存活的Fragment数量,降低内存占用。
  3. 避免主线程阻塞:所有媒体资源的加载、释放操作都放到子线程处理,绝对不要在主线程做IO操作。
  4. 内存泄漏检测:用LeakCanary检测MediaPlayer和Fragment的内存泄漏,确保资源被彻底释放。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:46:58