Android RecyclerView视频滑块:解决视频切换后重复加载问题
解决RecyclerView视频切换后重新加载的问题
核心问题分析
RecyclerView默认会回收超出屏幕范围的ViewHolder,导致视频项滑出后再返回时,onBindViewHolder会重新执行,现有代码每次绑定都会重新设置视频URI,触发重新加载。同时VideoView本身没有内置状态持久化机制,需要手动保存和恢复播放进度与状态。
解决方案步骤
1. 调整RecyclerView缓存策略,避免ViewHolder被回收
修改Fragment中RecyclerView的配置,让它保留所有列表项的ViewHolder,防止回收:
// Setup StoryBoardAdapter val storyboardAdapter = StoryBoardAdapter(requireContext(), DataRepository.storyBoardList) val layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false) binding.recyclerViewStoryBoard.apply { this.layoutManager = layoutManager this.adapter = storyboardAdapter // 固定列表大小,优化缓存逻辑 setHasFixedSize(true) // 设置缓存大小为列表总数量,确保所有ViewHolder都被保留 itemViewCacheSize = DataRepository.storyBoardList.size } // 设置预加载数量为列表总数量,提前加载所有项 layoutManager.initialPrefetchItemCount = DataRepository.storyBoardList.size // Attach PagerSnapHelper to show one item at a time val pagerSnapHelper = PagerSnapHelper() pagerSnapHelper.attachToRecyclerView(binding.recyclerViewStoryBoard)
2. 在Adapter中添加视频播放状态管理
定义存储播放状态的数据类,并在Adapter中维护每个视频的状态,实现状态的保存与恢复:
class StoryBoardAdapter( private val context: Context, private val storyBoardList: List<StoryBoard> ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { companion object { private const val TYPE_IMAGE = 0 private const val TYPE_VIDEO = 1 private const val BASE_URL = "Link" } // 存储每个视频的播放状态:key为视频完整URL,value为状态对象 private data class VideoPlaybackState( var isPrepared: Boolean = false, var currentPosition: Int = 0, var isPlaying: Boolean = false ) private val videoPlaybackStates = mutableMapOf<String, VideoPlaybackState>() private fun isVideo(url: String): Boolean { val lowerUrl = url.lowercase() return lowerUrl.endsWith(".mp4") || lowerUrl.endsWith(".webm") } private fun getFullUrl(relativeUrl: String): String { return if (relativeUrl.startsWith("http")) relativeUrl else "$BASE_URL$relativeUrl" } override fun getItemViewType(position: Int): Int { return if (isVideo(storyBoardList[position].imageEn)) TYPE_VIDEO else TYPE_IMAGE } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return if (viewType == TYPE_IMAGE) { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_story_board_image, parent, false) ImageViewHolder(view) } else { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_story_board_video, parent, false) VideoViewHolder(view) } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val item = storyBoardList[position] val currentLanguage = context.resources.configuration.locales[0].language val imageUrl = if (currentLanguage == "ar") item.imageAr else item.imageEn val fullUrl = getFullUrl(imageUrl) if (holder is ImageViewHolder) { Glide.with(holder.imageView.context) .load(fullUrl) .into(holder.imageView) } else if (holder is VideoViewHolder) { val videoState = videoPlaybackStates.getOrPut(fullUrl) { VideoPlaybackState() } if (!videoState.isPrepared) { // 首次加载视频 val videoUri = Uri.parse(fullUrl) holder.videoView.tag = fullUrl holder.videoView.setVideoURI(videoUri) holder.videoView.setOnPreparedListener { mediaPlayer -> mediaPlayer.isLooping = true videoState.isPrepared = true // 恢复之前保存的播放位置 if (videoState.currentPosition > 0) { mediaPlayer.seekTo(videoState.currentPosition) } // 恢复播放状态 if (videoState.isPlaying) { holder.videoView.start() } } } else { // 视频已加载,直接恢复状态 holder.videoView.seekTo(videoState.currentPosition) if (videoState.isPlaying) { holder.videoView.start() } else { holder.videoView.pause() } } // 监听播放状态变化,实时更新状态存储 holder.videoView.setOnPlaybackStateChangedListener { mp, state -> videoState.isPlaying = state == MediaPlayer.STATE_PLAYING if (videoState.isPlaying) { videoState.currentPosition = mp.currentPosition } } } } // 当ViewHolder被回收时,保存当前播放状态 override fun onViewRecycled(holder: RecyclerView.ViewHolder) { super.onViewRecycled(holder) if (holder is VideoViewHolder) { val videoUrl = holder.videoView.tag as? String ?: return val videoState = videoPlaybackStates[videoUrl] ?: return // 保存当前位置和播放状态 videoState.currentPosition = holder.videoView.currentPosition videoState.isPlaying = holder.videoView.isPlaying // 暂停播放并释放资源 holder.videoView.pause() holder.videoView.stopPlayback() } } override fun getItemCount(): Int = storyBoardList.size inner class ImageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val imageView: ImageView = itemView.findViewById(R.id.imageViewItem) } inner class VideoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val videoView: VideoView = itemView.findViewById(R.id.videoViewItem) } }
3. 额外优化建议
- 如果视频数量较多,不建议一次性加载所有视频(会占用大量内存),可改为预加载当前项前后2-3个视频,平衡内存占用和用户体验。
- 考虑使用
ExoPlayer替代VideoView,ExoPlayer提供了更完善的缓存、状态管理和生命周期控制能力,能更好适配RecyclerView场景。
内容的提问来源于stack exchange,提问作者user24936837
相关产品推荐
相关产品推荐

