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
相关产品推荐
相关产品推荐

