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

Android RecyclerView实现图文混排及视频自动播放与续播需求

Solution for Social-Media Style RecyclerView with Image/Video Playback

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 09:07:43