视频截取中的UI小组件

打印 上一主题 下一主题

主题 542|帖子 542|积分 1626


引言

视频截取在社交类 APP 中十分常见。有了上传视频的功能,就不可避免地必要提供截取和编辑的选项。假如我们过度依靠第三方库,项目的代码可能会变得异常痴肥,因为这些库每每包含很多我们用不到的功能,而且它们的 UI 样式和功能通常比力固定,不支持定制。因此,有条件的话,尽可能自行实现这些功能。
原本我计划直接先容视频截取的实现方式,但发现相关的 UI 计划也非常风趣。假如不把 UI 和视频截取功能联合起来,即使掌握了截取技术,也可能难以打造出一个好用的视频编辑工具。因此,在本篇博客中,我们先来先容视频截取中最常见的 UI 样式和小组件。
组件结构


我们创建一个继承自UIView的SVVideoEditBar类,整个编辑的操作小组件可以分为播放控制和截取控制两部分:
播放控制

第一部分是播放和停息按钮,控制截取后视频的播放和停息功能,这里比力简单只必要一个按钮就可以实现。
  1.     /// 播放按钮
  2.     private var playButton = UIButton()
复制代码
  1.     /// 添加播放按钮
  2.     private func addPlayerButton() {
  3.         playButton.setImage(UIImage(named: "icon_play_light_fill_24"), for: .normal)
  4.         playButton.setImage(UIImage(named: "icon_pause_light_fill_24"), for: .selected)
  5.         self.addSubview(playButton)
  6.         playButton.snp.makeConstraints { make in
  7.             make.leading.equalToSuperview().offset(16.0)
  8.             make.centerY.equalToSuperview()
  9.             make.width.height.equalTo(24.0)
  10.         }
  11.     }
复制代码
截取控制

第二部分为截取控制部分可以再详细分别为展示部分和操作部分。
展示

对于展示部分我们接纳UICollectionView来显示视频获取到的缩略图。
  1.     /// 列表
  2.     private var collectionView:UICollectionView!
复制代码
  1.     /// 添加列表
  2.     private func addCollectionView() {
  3.         let layout = UICollectionViewFlowLayout()
  4.         layout.minimumLineSpacing = 0.0
  5.         layout.scrollDirection = .horizontal
  6.         collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
  7.         collectionView.contentInset = UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0)
  8.         collectionView.backgroundColor = UIColor.clear
  9.         collectionView.showsHorizontalScrollIndicator = false
  10.         collectionView.delegate = self
  11.         collectionView.dataSource = self
  12.         collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
  13.         self.addSubview(collectionView)
  14.         collectionView.snp.makeConstraints { make in
  15.             make.leading.equalTo(lineView.snp.trailing).offset(20.0)
  16.             make.trailing.equalToSuperview().offset(-20.0)
  17.             make.top.equalToSuperview().offset(8.0)
  18.             make.bottom.equalToSuperview().offset(-8.0)
  19.         }
  20.         collectionView.backgroundColor = .red
  21.     }
复制代码
关于列表中图片的尺寸会根据视频的尺寸来确定,以是我们将大小在代理方法中进行设置
  1. extension SVVideoEditBar: UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
  2.     func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  3.         return 10
  4.     }
  5.    
  6.     func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  7.         let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
  8.         return cell
  9.     }
  10.    
  11.     func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
  12.         
  13.         return CGSize(width: 40.0, height: collectionView.bounds.height)
  14.     }
  15. }
复制代码
滑动遮罩

遮罩也分为两部分,一部分为黄色的边框,边框内的内容表现的是视频截取后保留的部分,而黄色边框以外的黑色半透明的遮罩则表现视频截取后舍弃的部分。
黄色的部分由我们自定义的视图SVEditorSliderView来实现。
  1.     /// 可拖拽滑动视图
  2.     private var sliderView = SVEditorSliderView()
复制代码
  1.     /// 添加滑动视图
  2.     private func addSliderView() {
  3.         self.addSubview(sliderView)
  4.         sliderView.layer.cornerRadius = 8.0
  5.         sliderView.backgroundColor = UIColor.yellow
  6.         updateLeftRightOffset()
  7.         // 右侧拖拽回调
  8.         sliderView.rightDragBlock = { [weak self] x in
  9.            ...
  10.         }
  11.         // 左侧拖拽回调
  12.         sliderView.leftDragBlock = { [weak self] x in
  13.           ....
  14.         }
  15.     }
复制代码
  1.     /// 更新左右侧偏移
  2.     private func updateLeftRightOffset() {
  3.         sliderView.snp.remakeConstraints { make in
  4.             make.leading.equalTo(lineView.snp.trailing).offset(leftOffset)
  5.             make.top.bottom.equalToSuperview()
  6.             make.trailing.equalToSuperview().offset(rightOffset)
  7.         }
  8.     }
复制代码
两侧的黑色半透明遮罩直接通过UIView设置背景颜色的方式来实现
  1.     /// 左侧蒙层
  2.     private var leftMaskView = UIView()
  3.     /// 右侧蒙层
  4.     private var rightMaskView = UIView()
复制代码
  1.     /// 添加左侧蒙层
  2.     private func addLeftMaskView() {
  3.         leftMaskView.backgroundColor = UIColor.black.withAlphaComponent(0.3)
  4.         self.addSubview(leftMaskView)
  5.         leftMaskView.snp.makeConstraints { make in
  6.             make.top.bottom.equalToSuperview()
  7.             make.leading.equalTo(self.lineView.snp.trailing)
  8.             make.trailing.equalTo(sliderView.snp.leading)
  9.         }
  10.     }
  11.    
  12.     /// 添加右侧蒙层
  13.     private func addRightMaskView() {
  14.         rightMaskView.backgroundColor = UIColor.black.withAlphaComponent(0.3)
  15.         self.addSubview(rightMaskView)
  16.         rightMaskView.snp.makeConstraints { make in
  17.             make.top.bottom.equalToSuperview()
  18.             make.leading.equalTo(sliderView.snp.trailing)
  19.             make.trailing.equalToSuperview()
  20.         }
  21.     }
复制代码
进度条

另外还有一个在播放时会跟随播放进度而移动的进度条,直接使用UIView加阴影的方式来实现。
  1. class SVProgressLineView: UIView {
  2.     override init(frame: CGRect) {
  3.         super.init(frame: frame)
  4.         self.backgroundColor = .white
  5.         self.layer.cornerRadius = 1.0
  6.         // 阴影
  7.         self.layer.shadowColor = UIColor.black.cgColor
  8.         self.layer.shadowOffset = CGSize(width: -1, height: 0)
  9.         self.layer.shadowOpacity = 0.5
  10.         self.layer.shadowRadius = 2.0
  11.     }
  12.    
  13.     required init?(coder: NSCoder) {
  14.         fatalError("init(coder:) has not been implemented")
  15.     }
  16.    
  17. }
复制代码
  1.     /// 进度线
  2.     private var progressLineView = SVProgressLineView()
复制代码
  1.     /// 添加进度线
  2.     private func addProgressLineView() {
  3.         self.addSubview(progressLineView)
  4.         progressLineView.snp.makeConstraints { make in
  5.             make.leading.equalTo(sliderView).offset(20.0 + 10.0)
  6.             make.centerY.equalToSuperview()
  7.             make.width.equalTo(2.0)
  8.             make.height.equalTo(self.snp.height).inset(8.0)
  9.         }
  10.     }
复制代码
实现操作视图 - SVEditorSliderView

接下来我们把重点集中到SVEditorSliderView上面,首先它必要一个镂空效果,来显示底部的缩图列表,另外它的两侧还必要可拖拽来修改视频的截取地区。
镂空效果

那我们先来实现它的镂空效果,接纳图层的mask属性和贝塞尔曲线联合来实现镂空。
  1.     /// maskLayer
  2.     private let maskLayer = CAShapeLayer()
  3.     /// path
  4.     private var path = UIBezierPath()
  5.     /// fullPath
  6.     private var fullPath = UIBezierPath()
复制代码
  1.     override init(frame: CGRect) {
  2.         super.init(frame: frame)
  3.         maskLayer.backgroundColor = UIColor.black.cgColor
  4.         self.layer.mask = maskLayer
  5.         maskLayer.fillRule = .evenOdd
  6.         ...
  7.     }
复制代码
  1.     override func layoutSubviews() {
  2.         let width = self.bounds.width
  3.         let height = self.bounds.height
  4.         fullPath = UIBezierPath(rect: self.bounds)
  5.         path = UIBezierPath(rect: CGRect(x: 20.0, y: 8.0, width: width - 40.0, height: height - 16.0))
  6.         fullPath.append(path)
  7.         fullPath.usesEvenOddFillRule = true
  8.         maskLayer.path = fullPath.cgPath
  9.         ....
  10.     }
复制代码
拖拽事件

在视图的最左侧和最右侧添加可拖拽的UIView视图,并处置处罚拖拽事件,由于该视图的布局在父视图上,以是我们选择将退拽事件回调出去来处置处罚。
  1.     /// 右侧可拖拽视图
  2.     private let rightDragView = UIView()
  3.     /// 左侧可拖拽视图
  4.     private let leftDragView = UIView()
  5.     /// 右侧退拽回调
  6.     var rightDragBlock:((CGFloat)->Void)?
  7.     /// 左侧拖拽回调
  8.     var leftDragBlock:((CGFloat)->Void)?
复制代码
  1.     /// 添加右侧可拖拽视图
  2.     private func addRightDragView() {
  3. //        rightDragView.backgroundColor = .white
  4.         self.addSubview(rightDragView)
  5.         let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction(_:)))
  6.         rightDragView.addGestureRecognizer(pan)
  7.         rightDragView.isUserInteractionEnabled = true
  8.     }
  9.    
  10.     /// 添加左侧可拖拽视图
  11.     private func addLeftDragView() {
  12. //        leftDragView.backgroundColor = .white
  13.         self.addSubview(leftDragView)
  14.         let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction(_:)))
  15.         leftDragView.addGestureRecognizer(pan)
  16.         leftDragView.isUserInteractionEnabled = true
  17.     }
  18.    
复制代码
  1.     @objc private func panAction(_ pan:UIPanGestureRecognizer) {
  2.         // 获取视图
  3.         let view = pan.view
  4.         if view == rightDragView {
  5.             let translation = pan.translation(in: self)
  6.             let x = translation.x
  7.             rightDragBlock?(x)
  8.         } else if view == leftDragView {
  9.             let translation = pan.translation(in: self)
  10.             let x = translation.x
  11.             leftDragBlock?(x)
  12.         }
  13.         pan.setTranslation(.zero, in: self)
  14.     }
复制代码
  1.     override func layoutSubviews() {
  2.         let width = self.bounds.width
  3.         let height = self.bounds.height
  4.         ...
  5.         rightDragView.frame = CGRect(x: width - 20.0, y: 0.0, width: 20.0, height: height)
  6.         leftDragView.frame = CGRect(x: 0.0, y: 0.0, width: 20.0, height: height)
  7.     }
复制代码
拖拽处置处罚

处置处罚拖拽事件是个细活,在拖拽过程中我们必要更新sliderView的布局约束,我们把它分成两个部分来讨论。
右侧拖拽事件

在SVVideoEditBar类中我们还定义另外两个CGFloat类型属性
       /// 左侧偏移
      private var leftOffset:CGFloat = 0.0
      /// 右侧偏移
      private var rightOffset:CGFloat = 0.0
  分别表现sliderView的左侧约束的偏移量和右侧约束的偏移量,默认都为0.0。
在进行右侧退拽时,我们首先必要注意的是,往右拖拽时不能凌驾SVVideoEditBar的最右端,而往左退拽时不能凌驾sliderView自己的最左端的拖拽视图。
  1.         // 右侧拖拽回调
  2.         sliderView.rightDragBlock = { [weak self] x in
  3.             guard let self = self else { return }
  4.             // 限制右侧往右的拖拽范围
  5.             if self.sliderView.frame.maxX >= self.bounds.width && x > 0 {
  6.                 print("右侧往右拖拽到最大范围")
  7.                 self.rightOffset = 0.0
  8.                 self.updateLeftRightOffset()
  9.                 return
  10.             }
  11.             // 限制右侧往左的拖拽范围
  12.             if self.sliderView.frame.maxX <= (self.sliderView.frame.minX+40.0) && x < 0 {
  13.                 print("右侧往左拖拽到最小范围")
  14.                 self.rightOffset = -(self.bounds.width - self.sliderView.frame.minX - 40.0)
  15.                 self.updateLeftRightOffset()
  16.                 return
  17.             }
  18.             self.rightOffset = self.rightOffset + x
  19.             self.updateLeftRightOffset()
  20.         }
复制代码
左侧拖拽事件

当我们拖拽左侧是视图时,必要注意当往左侧拖拽时不能凌驾左侧的起始位置,也就是竖线的最最右侧。而往右拖拽时同样也不能凌驾右侧的拖拽视图。
  1.         // 左侧拖拽回调
  2.         sliderView.leftDragBlock = { [weak self] x in
  3.             guard let self = self else { return }
  4.             // 限制左侧往左的拖拽范围
  5.             if self.sliderView.frame.minX <= self.lineView.frame.maxX && x < 0 {
  6.                 print("左侧往左拖拽到最小范围")
  7.                 self.leftOffset = 0.0
  8.                 self.updateLeftRightOffset()
  9.                 return
  10.             }
  11.             // 限制左侧往右的拖拽范围
  12.             if self.sliderView.frame.minX >= (self.sliderView.frame.maxX-40.0) && x > 0 {
  13.                 print("左侧往右拖拽到最大范围")
  14.                 self.leftOffset = self.sliderView.frame.maxX - 40.0 - self.lineView.frame.maxX
  15.                 self.updateLeftRightOffset()
  16.                 return
  17.             }
  18.             self.leftOffset = self.leftOffset + x
  19.             self.updateLeftRightOffset()
  20.         }
复制代码
这样像GIF图片一样的视频剪裁小组件的UI部分就实现了。

结语

本篇博客主要先容了视频截取中的UI小组件,先容了怎样实现镂空效果,以及拖拽事件,尤其是拖拽时临界值的处置处罚。
获取到了裁剪地区之后,我们就可以根据视频的长度来进行视频截取了,那么下一篇博客我们将开始进入视频截取的数据处置处罚部分。




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

曂沅仴駦

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表