高仿剪映视频多轨剪辑页实现

您所在的位置:网站首页 抖音怎么剪视频时间 高仿剪映视频多轨剪辑页实现

高仿剪映视频多轨剪辑页实现

2024-06-11 10:20| 来源: 网络整理| 查看: 265

剪映是当下比较火的一款手机视频剪辑工具,由抖音官方推出,可用于手机短视频的剪辑制作,拥有强大的多轨编辑能力。其中视频剪辑页用于剪辑的View拥有出色的交互性,很考验Android的基础能力,值得拿出来学习一下。  观察剪映的视频剪辑页面,可见主要有时间轴、视频轨道、时间游标和预览窗口四部分组成。时间轴用于展示当前的时间长度和时间刻度,通过缩放手势可以改变最小刻度值,拖动可以对音视频进行seek。视频轨道用于显示轨道在时间轴上的长度、以及轨道信息,同时视频轨道会显示对应时间的帧图像,而音频轨道则会显示波形图。时间游标会固定在整个View的中间位置,虽然叫它游标,但实际上并不会移动,只能通过移动时间轴和视频轨道来表示当前的时间位置。预览窗口用于显示视频帧,通常是SurfaceView或TextureView,比较简单,非本文的重点。

实现

本文并不会完全通过Canvas绘制每一个UI元素,而是尽可能利用Android现有的View进行组合实现,虽然性能较低,但实现起来简单。整个View结构分三层:

AlTrackContainer作为整个View的根,继承自HorizontalScrollView以实现水平滚动,同时负责缩放手势处理以及时间游标的绘制。

AlTrackView负责组织时间轴和各个视频轨道的布局,同时响应缩放手势,实时改变子View的长度。

AlTimelineView作为时间轴,负责绘制时间刻度,同时响应缩放手势,实时改变时间刻度和长度。

AlTrackItemView单纯继承自TextView,用于显示轨道名称以及音频的波形。

时间轴

AlTimelineView由时间刻度和圆点组成,时间刻度格式为##:##,值得注意的是刻度与圆点之间有一个最小和最大间距,这里把刻度与圆点距离、最小和最大间距分别定义为Space、MinSpace和MaxSpace,Space总是大于MinSpace,小于MaxSpace,其中MaxSpace=MinSpace*4+圆点直径+刻度文字宽度,以便于Space>MaxSpace时,正好能够增加显示一个时间刻度。

根据View的宽度、##:##宽度以及Space与MinSpace、MaxSpace的关系初始化刻度值,并把每个刻度值的String保存到一个数组。

当通过缩放手势放大时间轴,刻度间距由小到大变化,直到Space>MaxSpace时,根据View的宽度、刻度宽度以及Space与MinSpace、MaxSpace的关系重新生成新的刻度,并覆盖保存到数组,如果计算得当的话,新的刻度Space总是大于MinSpace,小于MaxSpace。

同理,当通过缩放手势放大时间轴,直到Space cursorRect.width()) { spaceSize = tmp return textVec.size } return Int.MIN_VALUE } private fun measureText(): Int { if (durationInUS 1) { spaceSize = (visibleWidth - textSize.x * count) / (count - 1).toFloat() for (i in 0 until count) { textVec.add(fmt.format(Date(i * durationInUS / (count - 1) / 1000))) } } else { spaceSize = (visibleWidth - textSize.x).toFloat() textVec.add(fmt.format(Date(0))) } return count } override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) val count = measureText() for (i in 0 until count) { val text = textVec[i] val x = paddingLeft - textSize.x / 2f + ((textSize.x + spaceSize) * i).toFloat() canvas?.drawText(text, x, (measuredHeight + textSize.y) / 2f, paint) if (i < count - 1) { canvas?.drawCircle( x + textSize.x + spaceSize / 2f, measuredHeight / 2f, cursorSize / 2f, paint ) } } }

视频轨道

AlTrackItemView由AlTrackView进行布局,AlTrackView同时页负责时间轴的摆放,功能比较简单。只需要保证AlTimelineView和AlTrackItemView的垂直线性布局即可,同时需要保证AlTrackItemView在时间轴下的占比,并且在缩放的同时成比例改变AlTrackItemView和AlTrackView的宽度。  首先AlTrackView需要有一个缩放接口,该接口输入一个缩放比例,比例改变的同时在onMeasure方法内部根据缩放系数改变自身宽度。

fun setScale(scale: AlRational) { this.scale.num = scale.num this.scale.den = scale.den requestLayout() } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) val width = MeasureSpec.getSize(widthMeasureSpec) val height = MeasureSpec.getSize(heightMeasureSpec) measureChildren(widthMeasureSpec, heightMeasureSpec) if (originWidth "Track ${track.id}" AlMediaType.TYPE_AUDIO -> "Track ${track.id}" else -> "Unknown Track" } vMap[track.id]?.setBackgroundColor( when (track.type) { AlMediaType.TYPE_VIDEO -> mVideoColor AlMediaType.TYPE_AUDIO -> mAudioColor else -> Color.RED } ) val padding = applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f).toInt() vMap[track.id]?.setPadding(padding, padding, padding, padding) addView(vMap[track.id], makeLayoutParams()) requestLayout() //显示音频轨道波形图 updateAudioTrack(track) }

最后通过在onLayout方法中对AlTimelineView和AlTrackItemView进行布局,这里会根据轨道的时长占总时长的比例来设置AlTrackItemView自身的宽度。

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { var height = 0 var w = measuredWidth var h = mTimeView.measuredHeight mTimeView.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY), h) mTimeView.layout(l, height, l + w, height + h) height += h vMap.forEach { val track = tMap[it.key] val view = it.value w = measuredWidth - paddingLeft - paddingRight h = view.measuredHeight var offset = 0 if (null != track && mTimeView.getDuration() > 0 && track.duration > 0) { offset = (track.seqIn * w / mTimeView.getDuration()).toInt() w = (track.duration * w / mTimeView.getDuration()).toInt() } view.layout(paddingLeft + l + offset, height, paddingLeft + l + w + offset, height + h) view.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY), h) height += h } }

AlTrackContainer

AlTrackContainer作为AlTrackView的直接父级,承载着横向滚动的功能,我们可以继承HorizontalScrollView实现。同时实现了缩放手势的监听,通过缩放手势计算缩放系数,层层传递到AlTrackView和AlTimelineView进行缩放响应。缩放手势的监听很简单,只需要使用Android提供的ScaleGestureDetector即可。

private val mScaleDetector = ScaleGestureDetector(context, mScaleListener) private val mScaleListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() { private var previousScaleFactor = 1f override fun onScaleBegin(detector: ScaleGestureDetector): Boolean { previousScaleFactor = 1f return super.onScaleBegin(detector) } override fun onScaleEnd(detector: ScaleGestureDetector?) { previousScaleFactor = 1f super.onScaleEnd(detector) } override fun onScale(detector: ScaleGestureDetector): Boolean { val anchor = PointF( detector.focusX * 2 / measuredWidth.toFloat() - 1f, -(detector.focusY * 2 / measuredHeight.toFloat() - 1f) ) scale = scale * detector.scaleFactor / previousScaleFactor previousScaleFactor = detector.scaleFactor //限制最大最小缩放系数 if (scale < 0.5f) { scale = 0.5f } if (scale > 3) { scale = 3f } //把缩放系数传给AlTrackView getChildView().setScale(AlRational((scale * 10000).toInt(), 10000)) return super.onScale(detector) } } override fun onTouchEvent(event: MotionEvent): Boolean { mScaleDetector.onTouchEvent(event) return super.onTouchEvent(event) }

同时AlTrackContainer还需要绘制中心的游标,用来标示当前的时间点,这里游标使用一个圆角矩形来表示。由于游标需要显示在所有元素的上方,如果在onDraw中绘制会被其它元素遮挡,所以需要在dispatchDraw中绘制。至此,高仿剪映多轨编辑View实现完成。

override fun dispatchDraw(canvas: Canvas?) { super.dispatchDraw(canvas) canvas?.drawRoundRect( scrollX + (measuredWidth - cursorSize) / 2, 0f, scrollX + (measuredWidth + cursorSize) / 2, measuredHeight.toFloat(), cursorSize / 2f, cursorSize / 2f, paint ) }

实际效果对比

高仿效果

剪映放大效果

总结

以上只是对剪映主要逻辑的实现,实际还缺失很多比较细微的功能,比如显示视频截图、删除移动轨道等,并且实际效果与剪映还有一些差异。希望通过本文能给读者学习Android自定义View带来一些帮助。最后附上源码:

AlTrackContainer

https://github.com/imalimin/hwvc/blob/develop/proj/hwvc_android/codec_native/src/main/java/com/lmy/hwvcnative/widget/AlTrackContainer.kt

AlTrackView

https://github.com/imalimin/hwvc/blob/develop/proj/hwvc_android/codec_native/src/main/java/com/lmy/hwvcnative/widget/AlTrackView.kt

AlTimelineView

https://github.com/imalimin/hwvc/blob/develop/proj/hwvc_android/codec_native/src/main/java/com/lmy/hwvcnative/widget/AlTimelineView.kt

Special

如果只是实现一个UI的交互功能,有点太缺乏挑战了。实际上本文不仅实现了用于编辑的交互UI,而且还实现了音视频多轨预览剪辑的逻辑。

支持同时添加多个音视频轨道进行播放预览!

支持剪映没有的多视频轨道图层移动和缩放,可以任意摆放各个视频轨道的位置!

支持常规的音视频Seek、暂停与播放等。

以上源码都开源在hwvc项目,感兴趣的读者可以点击查看原文自取。

技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。

推荐阅读:

音视频面试基础题

OpenGL ES 学习资源分享

一文读懂 YUV 的采样与格式

OpenGL 之 GPUImage 源码分析

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3