网站项目根据什么开发,郑州网站建设代理,宜昌有做网站的公司吗,广告公司名称推荐引言
视频截取在社交类 APP 中十分常见。有了上传视频的功能#xff0c;就不可避免地需要提供截取和编辑的选项。如果我们过度依赖第三方库#xff0c;项目的代码可能会变得异常臃肿#xff0c;因为这些库往往包含许多我们用不到的功能#xff0c;而且它们的 UI 样式和功能…
引言
视频截取在社交类 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 inmake.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.0layout.scrollDirection .horizontalcollectionView UICollectionView(frame: .zero, collectionViewLayout: layout)collectionView.contentInset UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0)collectionView.backgroundColor UIColor.clearcollectionView.showsHorizontalScrollIndicator falsecollectionView.delegate selfcollectionView.dataSource selfcollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cell)self.addSubview(collectionView)collectionView.snp.makeConstraints { make inmake.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.0sliderView.backgroundColor UIColor.yellowupdateLeftRightOffset()// 右侧拖拽回调sliderView.rightDragBlock { [weak self] x in...}// 左侧拖拽回调sliderView.leftDragBlock { [weak self] x in....}} /// 更新左右侧偏移private func updateLeftRightOffset() {sliderView.snp.remakeConstraints { make inmake.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 inmake.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 inmake.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 .whiteself.layer.cornerRadius 1.0// 阴影self.layer.shadowColor UIColor.black.cgColorself.layer.shadowOffset CGSize(width: -1, height: 0)self.layer.shadowOpacity 0.5self.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 inmake.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属性和贝塞尔曲线结合来实现镂空。 /// maskLayerprivate let maskLayer CAShapeLayer()/// pathprivate var path UIBezierPath()/// fullPathprivate var fullPath UIBezierPath() override init(frame: CGRect) {super.init(frame: frame)maskLayer.backgroundColor UIColor.black.cgColorself.layer.mask maskLayermaskLayer.fillRule .evenOdd...} override func layoutSubviews() {let width self.bounds.widthlet height self.bounds.heightfullPath 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 truemaskLayer.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 .whiteself.addSubview(rightDragView)let pan UIPanGestureRecognizer(target: self, action: #selector(panAction(_:)))rightDragView.addGestureRecognizer(pan)rightDragView.isUserInteractionEnabled true}/// 添加左侧可拖拽视图private func addLeftDragView() {
// leftDragView.backgroundColor .whiteself.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.viewif view rightDragView {let translation pan.translation(in: self)let x translation.xrightDragBlock?(x)} else if view leftDragView {let translation pan.translation(in: self)let x translation.xleftDragBlock?(x)}pan.setTranslation(.zero, in: self)}override func layoutSubviews() {let width self.bounds.widthlet 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 inguard let self self else { return }// 限制右侧往右的拖拽范围if self.sliderView.frame.maxX self.bounds.width x 0 {print(右侧往右拖拽到最大范围)self.rightOffset 0.0self.updateLeftRightOffset()return}// 限制右侧往左的拖拽范围if self.sliderView.frame.maxX (self.sliderView.frame.minX40.0) x 0 {print(右侧往左拖拽到最小范围)self.rightOffset -(self.bounds.width - self.sliderView.frame.minX - 40.0)self.updateLeftRightOffset()return}self.rightOffset self.rightOffset xself.updateLeftRightOffset()}左侧拖拽事件
当我们拖拽左侧是视图时需要注意当往左侧拖拽时不能超过左侧的起始位置也就是竖线的最最右侧。而往右拖拽时同样也不能超过右侧的拖拽视图。 // 左侧拖拽回调sliderView.leftDragBlock { [weak self] x inguard let self self else { return }// 限制左侧往左的拖拽范围if self.sliderView.frame.minX self.lineView.frame.maxX x 0 {print(左侧往左拖拽到最小范围)self.leftOffset 0.0self.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.maxXself.updateLeftRightOffset()return}self.leftOffset self.leftOffset xself.updateLeftRightOffset()}这样像GIF图片一样的视频剪裁小组件的UI部分就实现了。 结语
本篇博客主要介绍了视频截取中的UI小组件介绍了如何实现镂空效果以及拖拽事件尤其是拖拽时临界值的处理。
获取到了裁剪区域之后我们就可以根据视频的长度来进行视频截取了那么下一篇博客我们将开始进入视频截取的数据处理部分。