引言
视频截取在社交类 APP 中十分常见。有了上传视频的功能,就不可避免地必要提供截取和编辑的选项。假如我们过度依靠第三方库,项目的代码可能会变得异常痴肥,因为这些库每每包含很多我们用不到的功能,而且它们的 UI 样式和功能通常比力固定,不支持定制。因此,有条件的话,尽可能自行实现这些功能。
原本我计划直接先容视频截取的实现方式,但发现相关的 UI 计划也非常风趣。假如不把 UI 和视频截取功能联合起来,即使掌握了截取技术,也可能难以打造出一个好用的视频编辑工具。因此,在本篇博客中,我们先来先容视频截取中最常见的 UI 样式和小组件。
组件结构
我们创建一个继承自UIView的SVVideoEditBar类,整个编辑的操作小组件可以分为播放控制和截取控制两部分:
播放控制
第一部分是播放和停息按钮,控制截取后视频的播放和停息功能,这里比力简单只必要一个按钮就可以实现。
- /// 播放按钮
- private var playButton = UIButton()
复制代码- /// 添加播放按钮
- private func addPlayerButton() {
- playButton.setImage(UIImage(named: "icon_play_light_fill_24"), for: .normal)
- playButton.setImage(UIImage(named: "icon_pause_light_fill_24"), for: .selected)
- self.addSubview(playButton)
- playButton.snp.makeConstraints { make in
- make.leading.equalToSuperview().offset(16.0)
- make.centerY.equalToSuperview()
- make.width.height.equalTo(24.0)
- }
- }
复制代码 截取控制
第二部分为截取控制部分可以再详细分别为展示部分和操作部分。
展示
对于展示部分我们接纳UICollectionView来显示视频获取到的缩略图。
- /// 列表
- private var collectionView:UICollectionView!
复制代码- /// 添加列表
- private func addCollectionView() {
- let layout = UICollectionViewFlowLayout()
- layout.minimumLineSpacing = 0.0
- layout.scrollDirection = .horizontal
- collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
- collectionView.contentInset = UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0)
- collectionView.backgroundColor = UIColor.clear
- collectionView.showsHorizontalScrollIndicator = false
- collectionView.delegate = self
- collectionView.dataSource = self
- collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
- self.addSubview(collectionView)
- collectionView.snp.makeConstraints { make in
- make.leading.equalTo(lineView.snp.trailing).offset(20.0)
- make.trailing.equalToSuperview().offset(-20.0)
- make.top.equalToSuperview().offset(8.0)
- make.bottom.equalToSuperview().offset(-8.0)
- }
- collectionView.backgroundColor = .red
- }
复制代码 关于列表中图片的尺寸会根据视频的尺寸来确定,以是我们将大小在代理方法中进行设置
- extension SVVideoEditBar: UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
- func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
- return 10
- }
-
- func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
- let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
- return cell
- }
-
- func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
-
- return CGSize(width: 40.0, height: collectionView.bounds.height)
- }
- }
复制代码 滑动遮罩
遮罩也分为两部分,一部分为黄色的边框,边框内的内容表现的是视频截取后保留的部分,而黄色边框以外的黑色半透明的遮罩则表现视频截取后舍弃的部分。
黄色的部分由我们自定义的视图SVEditorSliderView来实现。
- /// 可拖拽滑动视图
- private var sliderView = SVEditorSliderView()
复制代码- /// 添加滑动视图
- private func addSliderView() {
- self.addSubview(sliderView)
- sliderView.layer.cornerRadius = 8.0
- sliderView.backgroundColor = UIColor.yellow
- updateLeftRightOffset()
- // 右侧拖拽回调
- sliderView.rightDragBlock = { [weak self] x in
- ...
- }
- // 左侧拖拽回调
- sliderView.leftDragBlock = { [weak self] x in
- ....
- }
- }
复制代码- /// 更新左右侧偏移
- private func updateLeftRightOffset() {
- sliderView.snp.remakeConstraints { make in
- make.leading.equalTo(lineView.snp.trailing).offset(leftOffset)
- make.top.bottom.equalToSuperview()
- make.trailing.equalToSuperview().offset(rightOffset)
- }
- }
复制代码 两侧的黑色半透明遮罩直接通过UIView设置背景颜色的方式来实现
- /// 左侧蒙层
- private var leftMaskView = UIView()
- /// 右侧蒙层
- private var rightMaskView = UIView()
复制代码- /// 添加左侧蒙层
- private func addLeftMaskView() {
- leftMaskView.backgroundColor = UIColor.black.withAlphaComponent(0.3)
- self.addSubview(leftMaskView)
- leftMaskView.snp.makeConstraints { make in
- make.top.bottom.equalToSuperview()
- make.leading.equalTo(self.lineView.snp.trailing)
- make.trailing.equalTo(sliderView.snp.leading)
- }
- }
-
- /// 添加右侧蒙层
- private func addRightMaskView() {
- rightMaskView.backgroundColor = UIColor.black.withAlphaComponent(0.3)
- self.addSubview(rightMaskView)
- rightMaskView.snp.makeConstraints { make in
- make.top.bottom.equalToSuperview()
- make.leading.equalTo(sliderView.snp.trailing)
- make.trailing.equalToSuperview()
- }
- }
复制代码 进度条
另外还有一个在播放时会跟随播放进度而移动的进度条,直接使用UIView加阴影的方式来实现。
- class SVProgressLineView: UIView {
- override init(frame: CGRect) {
- super.init(frame: frame)
- self.backgroundColor = .white
- self.layer.cornerRadius = 1.0
- // 阴影
- self.layer.shadowColor = UIColor.black.cgColor
- self.layer.shadowOffset = CGSize(width: -1, height: 0)
- self.layer.shadowOpacity = 0.5
- self.layer.shadowRadius = 2.0
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- }
复制代码- /// 进度线
- private var progressLineView = SVProgressLineView()
复制代码- /// 添加进度线
- private func addProgressLineView() {
- self.addSubview(progressLineView)
- progressLineView.snp.makeConstraints { make in
- make.leading.equalTo(sliderView).offset(20.0 + 10.0)
- make.centerY.equalToSuperview()
- make.width.equalTo(2.0)
- make.height.equalTo(self.snp.height).inset(8.0)
- }
- }
复制代码 实现操作视图 - SVEditorSliderView
接下来我们把重点集中到SVEditorSliderView上面,首先它必要一个镂空效果,来显示底部的缩图列表,另外它的两侧还必要可拖拽来修改视频的截取地区。
镂空效果
那我们先来实现它的镂空效果,接纳图层的mask属性和贝塞尔曲线联合来实现镂空。
- /// maskLayer
- private let maskLayer = CAShapeLayer()
- /// path
- private var path = UIBezierPath()
- /// fullPath
- private var fullPath = UIBezierPath()
复制代码- override init(frame: CGRect) {
- super.init(frame: frame)
- maskLayer.backgroundColor = UIColor.black.cgColor
- self.layer.mask = maskLayer
- maskLayer.fillRule = .evenOdd
- ...
- }
复制代码- override func layoutSubviews() {
- let width = self.bounds.width
- let height = self.bounds.height
- fullPath = UIBezierPath(rect: self.bounds)
- path = UIBezierPath(rect: CGRect(x: 20.0, y: 8.0, width: width - 40.0, height: height - 16.0))
- fullPath.append(path)
- fullPath.usesEvenOddFillRule = true
- maskLayer.path = fullPath.cgPath
- ....
- }
复制代码 拖拽事件
在视图的最左侧和最右侧添加可拖拽的UIView视图,并处置处罚拖拽事件,由于该视图的布局在父视图上,以是我们选择将退拽事件回调出去来处置处罚。
- /// 右侧可拖拽视图
- private let rightDragView = UIView()
- /// 左侧可拖拽视图
- private let leftDragView = UIView()
- /// 右侧退拽回调
- var rightDragBlock:((CGFloat)->Void)?
- /// 左侧拖拽回调
- var leftDragBlock:((CGFloat)->Void)?
复制代码- /// 添加右侧可拖拽视图
- private func addRightDragView() {
- // rightDragView.backgroundColor = .white
- self.addSubview(rightDragView)
- let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction(_:)))
- rightDragView.addGestureRecognizer(pan)
- rightDragView.isUserInteractionEnabled = true
- }
-
- /// 添加左侧可拖拽视图
- private func addLeftDragView() {
- // leftDragView.backgroundColor = .white
- self.addSubview(leftDragView)
- let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction(_:)))
- leftDragView.addGestureRecognizer(pan)
- leftDragView.isUserInteractionEnabled = true
- }
-
复制代码- @objc private func panAction(_ pan:UIPanGestureRecognizer) {
- // 获取视图
- let view = pan.view
- if view == rightDragView {
- let translation = pan.translation(in: self)
- let x = translation.x
- rightDragBlock?(x)
- } else if view == leftDragView {
- let translation = pan.translation(in: self)
- let x = translation.x
- leftDragBlock?(x)
- }
- pan.setTranslation(.zero, in: self)
- }
复制代码- override func layoutSubviews() {
- let width = self.bounds.width
- let height = self.bounds.height
- ...
- rightDragView.frame = CGRect(x: width - 20.0, y: 0.0, width: 20.0, height: height)
- leftDragView.frame = CGRect(x: 0.0, y: 0.0, width: 20.0, height: height)
- }
复制代码 拖拽处置处罚
处置处罚拖拽事件是个细活,在拖拽过程中我们必要更新sliderView的布局约束,我们把它分成两个部分来讨论。
右侧拖拽事件
在SVVideoEditBar类中我们还定义另外两个CGFloat类型属性
/// 左侧偏移
private var leftOffset:CGFloat = 0.0
/// 右侧偏移
private var rightOffset:CGFloat = 0.0
分别表现sliderView的左侧约束的偏移量和右侧约束的偏移量,默认都为0.0。
在进行右侧退拽时,我们首先必要注意的是,往右拖拽时不能凌驾SVVideoEditBar的最右端,而往左退拽时不能凌驾sliderView自己的最左端的拖拽视图。
- // 右侧拖拽回调
- sliderView.rightDragBlock = { [weak self] x in
- guard let self = self else { return }
- // 限制右侧往右的拖拽范围
- if self.sliderView.frame.maxX >= self.bounds.width && x > 0 {
- print("右侧往右拖拽到最大范围")
- self.rightOffset = 0.0
- self.updateLeftRightOffset()
- return
- }
- // 限制右侧往左的拖拽范围
- if self.sliderView.frame.maxX <= (self.sliderView.frame.minX+40.0) && x < 0 {
- print("右侧往左拖拽到最小范围")
- self.rightOffset = -(self.bounds.width - self.sliderView.frame.minX - 40.0)
- self.updateLeftRightOffset()
- return
- }
- self.rightOffset = self.rightOffset + x
- self.updateLeftRightOffset()
- }
复制代码 左侧拖拽事件
当我们拖拽左侧是视图时,必要注意当往左侧拖拽时不能凌驾左侧的起始位置,也就是竖线的最最右侧。而往右拖拽时同样也不能凌驾右侧的拖拽视图。
- // 左侧拖拽回调
- sliderView.leftDragBlock = { [weak self] x in
- guard let self = self else { return }
- // 限制左侧往左的拖拽范围
- if self.sliderView.frame.minX <= self.lineView.frame.maxX && x < 0 {
- print("左侧往左拖拽到最小范围")
- self.leftOffset = 0.0
- self.updateLeftRightOffset()
- return
- }
- // 限制左侧往右的拖拽范围
- if self.sliderView.frame.minX >= (self.sliderView.frame.maxX-40.0) && x > 0 {
- print("左侧往右拖拽到最大范围")
- self.leftOffset = self.sliderView.frame.maxX - 40.0 - self.lineView.frame.maxX
- self.updateLeftRightOffset()
- return
- }
- self.leftOffset = self.leftOffset + x
- self.updateLeftRightOffset()
- }
复制代码 这样像GIF图片一样的视频剪裁小组件的UI部分就实现了。
结语
本篇博客主要先容了视频截取中的UI小组件,先容了怎样实现镂空效果,以及拖拽事件,尤其是拖拽时临界值的处置处罚。
获取到了裁剪地区之后,我们就可以根据视频的长度来进行视频截取了,那么下一篇博客我们将开始进入视频截取的数据处置处罚部分。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |