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

请求帮助:Exoplayer与ViewPager2的全屏滑动视频集成实现

没问题!我刚好做过类似的全屏滑动视频列表需求,给你一套简洁可靠的实现方案,核心是搞定ViewPager2的页面切换逻辑和ExoPlayer的生命周期管理,避免内存泄漏和播放异常。

ExoPlayer + ViewPager2 全屏视频列表实现方案

1. 添加依赖

首先在你的build.gradle(Module级别)里添加必要的依赖:

// ViewPager2
implementation "androidx.viewpager2:viewpager2:1.0.0"
// ExoPlayer核心库
implementation "com.google.android.exoplayer:exoplayer:2.19.1"
// ExoPlayer UI组件(带控制栏的PlayerView)
implementation "com.google.android.exoplayer:exoplayer-ui:2.19.1"

2. 布局文件

主布局(activity_main.xml)

放一个占满全屏的ViewPager2:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

视频页面布局(fragment_video.xml)

每个滑动页面是全屏的PlayerView:

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.exoplayer2.ui.PlayerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/playerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000"/>

3. 定义视频数据类

先搞个简单的数据类来存储视频信息:

data class VideoItem(
    val videoUrl: String,
    val title: String // 可选,用来展示视频标题
)

4. 实现视频Fragment

每个Fragment独立管理自己的ExoPlayer实例,严格处理生命周期:

class VideoFragment : Fragment() {
    private var player: ExoPlayer? = null
    private var playerView: PlayerView? = null
    private var videoItem: VideoItem? = null
    private var playbackPosition: Long = 0L // 保存播放进度

    companion object {
        private const val ARG_VIDEO_ITEM = "video_item"
        fun newInstance(videoItem: VideoItem): VideoFragment {
            val fragment = VideoFragment()
            val args = Bundle()
            args.putParcelable(ARG_VIDEO_ITEM, videoItem)
            fragment.arguments = args
            return fragment
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        videoItem = arguments?.getParcelable(ARG_VIDEO_ITEM)
        // 恢复保存的播放进度
        savedInstanceState?.let { playbackPosition = it.getLong("playback_position") }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_video, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        playerView = view.findViewById(R.id.playerView)
    }

    // 初始化播放器
    private fun initializePlayer() {
        videoItem ?: return
        player = ExoPlayer.Builder(requireContext()).build().apply {
            val mediaItem = MediaItem.fromUri(videoItem!!.videoUrl)
            setMediaItem(mediaItem)
            seekTo(playbackPosition)
            prepare()
            playerView?.player = this
        }
    }

    // 释放播放器资源
    private fun releasePlayer() {
        player?.let {
            playbackPosition = it.currentPosition
            it.release()
        }
        player = null
        playerView?.player = null
    }

    // 页面可见时恢复播放
    override fun onResume() {
        super.onResume()
        if (player == null) {
            initializePlayer()
        }
        player?.play()
    }

    // 页面暂停时暂停播放
    override fun onPause() {
        super.onPause()
        player?.pause()
    }

    // 页面销毁时彻底释放资源
    override fun onDestroyView() {
        super.onDestroyView()
        releasePlayer()
        playerView = null
    }

    // 保存播放进度,适配屏幕旋转
    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putLong("playback_position", playbackPosition)
    }
}

5. 实现ViewPager2的Adapter

用FragmentStateAdapter来管理所有视频Fragment:

class VideoPagerAdapter(fragmentActivity: FragmentActivity, private val videoList: List<VideoItem>) :
    FragmentStateAdapter(fragmentActivity) {

    override fun getItemCount(): Int = videoList.size

    override fun createFragment(position: Int): Fragment {
        return VideoFragment.newInstance(videoList[position])
    }
}

6. 在Activity中配置ViewPager2

最后在Activity里初始化ViewPager2,设置Adapter并监听页面切换,确保只有当前页面的视频在播放:

class MainActivity : AppCompatActivity() {
    private lateinit var viewPager: ViewPager2
    private val videoList = listOf(
        VideoItem("https://example.com/video1.mp4", "视频1"),
        VideoItem("https://example.com/video2.mp4", "视频2"),
        VideoItem("https://example.com/video3.mp4", "视频3")
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewPager = findViewById(R.id.viewPager)
        viewPager.adapter = VideoPagerAdapter(this, videoList)

        // 设置预加载数量,前后各预加载1个页面
        viewPager.offscreenPageLimit = 1

        // 监听页面切换,暂停非当前页面的播放
        viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                // 遍历所有已加载的Fragment,暂停非当前页面的视频
                supportFragmentManager.fragments.forEach { fragment ->
                    if (fragment is VideoFragment && fragment != supportFragmentManager.findFragmentByTag("f$position")) {
                        fragment.player?.pause()
                    }
                }
                // 恢复当前页面的播放
                val currentFragment = supportFragmentManager.findFragmentByTag("f$position")
                if (currentFragment is VideoFragment) {
                    currentFragment.player?.play()
                }
            }
        })
    }
}

关键注意事项

  • 一定要释放资源:别忘在Fragment的onDestroyView里释放ExoPlayer实例,不然轻则内存泄漏,重则导致播放崩溃。
  • 页面切换要控播放:通过ViewPager2的页面切换回调,确保只有当前可见的视频在播放,既节省系统资源,也符合用户观看习惯。
  • 屏幕旋转适配:代码里已经加了播放进度保存逻辑,横竖屏切换时不会丢失进度。
  • 预加载别贪多:offscreenPageLimit别设太大,1就足够,不然会占用过多内存导致卡顿。

内容的提问来源于stack exchange,提问作者Arsalan Fakhar Siddiqui

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 13:02:47