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

Android视频编辑器时间线视图:RecyclerView关联视频进度难题

解决方案

针对你的需求,这里提供两种可行的实现方案,解决视频进度与横竖滚动时间线的同步问题:


方案一:独立Item横向滚动(多轨道片段场景)

适合每个RecyclerView Item对应视频的不同时间片段(如剪辑软件的多轨道时间线),每个Item自带横向滚动容器,单独同步对应片段的进度。

1. 改造CustomView

给自定义视图添加进度更新、时间范围绑定及交互监听:

class CustomView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    private lateinit var horizontalScrollView: HorizontalScrollView
    private lateinit var customSeekBar: CustomSeekBar2
    var segmentStartTime: Long = 0L // 当前Item对应的视频起始时间
    var segmentDuration: Long = 0L // 当前Item对应的片段时长

    init {
        inflate(context, R.layout.your_custom_view_layout, this)
        horizontalScrollView = findViewById(R.id.horizontalScrollView)
        customSeekBar = findViewById(R.id.customVideoSeekBar)

        // 监听SeekBar手动拖动,同步到ExoPlayer
        customSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
                if (fromUser) {
                    val targetPosition = segmentStartTime + (segmentDuration * progress / 100).toLong()
                    onSeekListener?.onSeekTo(targetPosition)
                }
            }
            override fun onStartTrackingTouch(seekBar: SeekBar) {}
            override fun onStopTrackingTouch(seekBar: SeekBar) {}
        })

        // 监听横向滚动,同步SeekBar进度
        horizontalScrollView.viewTreeObserver.addOnScrollChangedListener {
            val totalScrollWidth = customSeekBar.width - horizontalScrollView.width
            if (totalScrollWidth > 0) {
                val progress = (horizontalScrollView.scrollX * 100 / totalScrollWidth)
                customSeekBar.progress = progress
            }
        }
    }

    // 根据视频当前位置更新Item进度与滚动位置
    fun syncWithVideoPosition(currentPosition: Long) {
        if (currentPosition < segmentStartTime || currentPosition > segmentStartTime + segmentDuration) return
        val relativeProgress = (currentPosition - segmentStartTime) * 100 / segmentDuration
        customSeekBar.progress = relativeProgress.toInt()
        
        val totalScrollWidth = customSeekBar.width - horizontalScrollView.width
        if (totalScrollWidth > 0) {
            val targetScrollX = (relativeProgress * totalScrollWidth / 100).toInt()
            horizontalScrollView.scrollTo(targetScrollX, 0)
        }
    }

    private var onSeekListener: OnSeekListener? = null
    fun setOnSeekListener(listener: OnSeekListener) {
        onSeekListener = listener
    }

    interface OnSeekListener {
        fun onSeekTo(videoPosition: Long)
    }
}

2. RecyclerView Adapter绑定数据与交互

class TimelineAdapter(
    private val segments: List<VideoSegment>,
    private val exoPlayer: ExoPlayer
) : RecyclerView.Adapter<TimelineAdapter.SegmentViewHolder>() {

    data class VideoSegment(val startTime: Long, val duration: Long)

    inner class SegmentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val customView: CustomView = itemView as CustomView

        init {
            customView.setOnSeekListener { position ->
                exoPlayer.seekTo(position)
            }
        }

        fun bind(segment: VideoSegment) {
            customView.segmentStartTime = segment.startTime
            customView.segmentDuration = segment.duration
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SegmentViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.your_custom_view_layout, parent, false)
        return SegmentViewHolder(view)
    }

    override fun onBindViewHolder(holder: SegmentViewHolder, position: Int) {
        holder.bind(segments[position])
    }

    override fun getItemCount() = segments.size
}

3. 监听ExoPlayer进度,同步到对应Item

在Activity/Fragment中添加进度监听,定时更新当前播放位置对应的Item:

private val progressHandler = Handler(Looper.getMainLooper())
private val progressUpdateRunnable = object : Runnable {
    override fun run() {
        val currentPosition = exoPlayer.currentPosition
        // 找到当前播放位置对应的Segment
        val targetSegmentPos = segments.indexOfFirst {
            currentPosition >= it.startTime && currentPosition <= it.startTime + it.duration
        }
        if (targetSegmentPos != -1) {
            // 更新对应Item的进度与滚动
            recyclerView.findViewHolderForAdapterPosition(targetSegmentPos)?.let { holder ->
                (holder as TimelineAdapter.SegmentViewHolder).customView.syncWithVideoPosition(currentPosition)
            }
            // 垂直滚动到当前Segment(可选)
            recyclerView.scrollToPosition(targetSegmentPos)
        }
        progressHandler.postDelayed(this, 100) // 每100ms更新一次,可调整
    }
}

// 绑定ExoPlayer状态监听
exoPlayer.addListener(object : Player.EventListener {
    override fun onPlaybackStateChanged(playbackState: Int) {
        when (playbackState) {
            Player.STATE_READY, Player.STATE_BUFFERING -> progressHandler.post(progressUpdateRunnable)
            else -> progressHandler.removeCallbacks(progressUpdateRunnable)
        }
    }
})

// 页面销毁时清理回调
override fun onDestroy() {
    super.onDestroy()
    progressHandler.removeCallbacks(progressUpdateRunnable)
}

方案二:全局横向滚动容器(统一时间轴场景)

适合所有RecyclerView Item共享同一全局时间轴,将横向滚动容器移到RecyclerView外层,实现全局同步,性能更优。

1. 修改布局结构

<HorizontalScrollView
    android:id="@+id/globalHorizontalScroll"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:orientation="vertical"/>

</HorizontalScrollView>

2. 计算时间轴宽度与进度同步

在Activity/Fragment中实现进度与滚动的双向同步:

private val totalVideoDuration = exoPlayer.duration
private val pixelPerSecond = 100 // 自定义:每秒对应100像素,可调整
private val totalTimelineWidth = (totalVideoDuration / 1000 * pixelPerSecond).toInt()

// 监听ExoPlayer进度,同步横向滚动
private val progressHandler = Handler(Looper.getMainLooper())
private val scrollSyncRunnable = object : Runnable {
    override fun run() {
        val currentPosition = exoPlayer.currentPosition
        val targetScrollX = (currentPosition * totalTimelineWidth / totalVideoDuration).toInt()
        globalHorizontalScroll.scrollTo(targetScrollX, 0)
        progressHandler.postDelayed(this, 100)
    }
}

// 监听横向滚动,同步到ExoPlayer
globalHorizontalScroll.viewTreeObserver.addOnScrollChangedListener {
    val scrollX = globalHorizontalScroll.scrollX
    val targetPosition = (scrollX * totalVideoDuration / totalTimelineWidth).toLong()
    exoPlayer.seekTo(targetPosition)
}

// 绑定ExoPlayer状态监听
exoPlayer.addListener(object : Player.EventListener {
    override fun onPlaybackStateChanged(playbackState: Int) {
        when (playbackState) {
            Player.STATE_READY, Player.STATE_BUFFERING -> progressHandler.post(scrollSyncRunnable)
            else -> progressHandler.removeCallbacks(scrollSyncRunnable)
        }
    }
})

override fun onDestroy() {
    super.onDestroy()
    progressHandler.removeCallbacks(scrollSyncRunnable)
}

内容的提问来源于stack exchange,提问作者YOUSEF -ELSAYED

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 00:43:08