请求帮助: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
相关产品推荐
相关产品推荐

