Android RecyclerView实现图文混排及视频自动播放与续播需求
Hey there! I’ve built similar social-media-style RecyclerViews with video playback before, so let’s walk through how to get this working—even if those libraries didn’t pan out. The key is focusing on visible-only playback, state persistence, and proper resource management to match Instagram/LinkedIn/Facebook’s behavior.
Core Principles to Follow
- Only play videos that are fully (or mostly) visible in the viewport
- Pause playback when the app goes to background, resume where it left off when returning
- Clean up video players when ViewHolders are recycled to avoid memory leaks
- Limit concurrent playback (most social apps only play one visible video at a time)
Step-by-Step Implementation
We’ll use ExoPlayer (Google’s recommended video player) since it’s more stable than Android’s default MediaPlayer and handles edge cases like format support better.
1. Define Your Media Model & Layout
First, create a data class to distinguish images and videos:
enum class MediaType { IMAGE, VIDEO } data class MediaItem(val type: MediaType, val url: String)
Then make a layout item_media.xml that includes both an ImageView and PlayerView:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/media_image" android:layout_width="match_parent" android:layout_height="250dp" android:scaleType="centerCrop"/> <com.google.android.exoplayer2.ui.PlayerView android:id="@+id/media_player" android:layout_width="match_parent" android:layout_height="250dp" android:visibility="gone"/> </FrameLayout>
2. Build a Reusable Media ViewHolder
Create a ViewHolder that manages the ExoPlayer instance, playback state, and cleanup:
class MediaViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val imageView: ImageView = itemView.findViewById(R.id.media_image) val playerView: PlayerView = itemView.findViewById(R.id.media_player) var exoPlayer: ExoPlayer? = null var currentVideoUrl: String? = null var playbackPosition: Long = 0L var isPlaying: Boolean = false // Clean up player when ViewHolder is recycled fun releasePlayer() { exoPlayer?.let { playbackPosition = it.currentPosition isPlaying = it.isPlaying it.release() } exoPlayer = null } }
3. Implement the Adapter with Playback Logic
The adapter will handle binding images/videos, and manage which video is playing based on visibility:
class MediaAdapter(private val mediaList: List<MediaItem>) : RecyclerView.Adapter<MediaViewHolder>() { var currentlyPlayingHolder: MediaViewHolder? = null private lateinit var recyclerView: RecyclerView override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { super.onAttachedToRecyclerView(recyclerView) this.recyclerView = recyclerView } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_media, parent, false) return MediaViewHolder(view) } override fun onBindViewHolder(holder: MediaViewHolder, position: Int) { val mediaItem = mediaList[position] when (mediaItem.type) { MediaType.IMAGE -> { holder.releasePlayer() holder.playerView.visibility = View.GONE holder.imageView.visibility = View.VISIBLE // Load image with Glide/Picasso: // Glide.with(holder.itemView.context).load(mediaItem.url).into(holder.imageView) } MediaType.VIDEO -> { holder.imageView.visibility = View.GONE holder.playerView.visibility = View.VISIBLE setupVideoPlayer(holder, mediaItem.url) } } } private fun setupVideoPlayer(holder: MediaViewHolder, videoUrl: String) { // Reuse existing player if the video URL is the same if (holder.currentVideoUrl == videoUrl) { holder.exoPlayer?.seekTo(holder.playbackPosition) if (holder.isPlaying) holder.exoPlayer?.play() return } // Release old player and set up new one holder.releasePlayer() holder.currentVideoUrl = videoUrl val context = holder.itemView.context val exoPlayer = ExoPlayer.Builder(context).build() holder.exoPlayer = exoPlayer holder.playerView.player = exoPlayer // Configure default social media behavior (muted, auto-play ready) exoPlayer.volume = 0f exoPlayer.setMediaItem(com.google.android.exoplayer2.MediaItem.fromUri(videoUrl)) exoPlayer.prepare() exoPlayer.pause() // Wait until visible to play } // Trigger playback for the first visible video when scrolling stops fun startPlayingVisibleVideo(layoutManager: LinearLayoutManager) { val firstVisiblePos = layoutManager.findFirstVisibleItemPosition() val lastVisiblePos = layoutManager.findLastVisibleItemPosition() // Pause previously playing video currentlyPlayingHolder?.apply { exoPlayer?.pause() isPlaying = false } // Find and play the first visible video for (pos in firstVisiblePos..lastVisiblePos) { val holder = recyclerView.findViewHolderForAdapterPosition(pos) as? MediaViewHolder if (holder != null && mediaList[pos].type == MediaType.VIDEO) { holder.exoPlayer?.play() holder.isPlaying = true currentlyPlayingHolder = holder break // Only play one video at a time (like Instagram) } } } override fun onViewRecycled(holder: MediaViewHolder) { super.onViewRecycled(holder) holder.releasePlayer() if (currentlyPlayingHolder == holder) currentlyPlayingHolder = null } }
4. Add Scroll & Lifecycle Handling
In your Activity/Fragment, hook up the RecyclerView to control playback on scroll and app lifecycle changes:
class MainActivity : AppCompatActivity() { private lateinit var recyclerView: RecyclerView private lateinit var adapter: MediaAdapter private lateinit var layoutManager: LinearLayoutManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Initialize sample media list (replace with your data) val mediaList = listOf( MediaItem(MediaType.IMAGE, "https://example.com/image1.jpg"), MediaItem(MediaType.VIDEO, "https://example.com/video1.mp4"), MediaItem(MediaType.IMAGE, "https://example.com/image2.jpg"), MediaItem(MediaType.VIDEO, "https://example.com/video2.mp4") ) recyclerView = findViewById(R.id.recycler_view) layoutManager = LinearLayoutManager(this) adapter = MediaAdapter(mediaList) recyclerView.layoutManager = layoutManager recyclerView.adapter = adapter // Control playback on scroll recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) if (newState == RecyclerView.SCROLL_STATE_IDLE) { adapter.startPlayingVisibleVideo(layoutManager) } else { // Pause scrolling video to save resources adapter.currentlyPlayingHolder?.exoPlayer?.pause() } } }) } // Handle app background/foreground state override fun onPause() { super.onPause() adapter.currentlyPlayingHolder?.apply { playbackPosition = exoPlayer?.currentPosition ?: 0L isPlaying = exoPlayer?.isPlaying ?: false exoPlayer?.pause() } } override fun onResume() { super.onResume() adapter.currentlyPlayingHolder?.apply { exoPlayer?.seekTo(playbackPosition) if (isPlaying) exoPlayer?.play() } } // Clean up all players when activity is destroyed override fun onDestroy() { super.onDestroy() adapter.currentlyPlayingHolder?.releasePlayer() } }
Key Optimizations & Tips
- Muted by Default: All social platforms play videos muted initially—add a toggle if needed.
- Preloading: For smooth playback, preload the next few videos using ExoPlayer’s cache.
- Error Handling: Add listeners to ExoPlayer for playback errors (e.g., broken URLs) and show a fallback image.
- Performance: Avoid playing multiple videos at once to prevent CPU/GPU overload.
内容的提问来源于stack exchange,提问作者Ritesh Karmare

