最近在做一款视频剪辑的app时,功能中会有视频裁剪,需要选取视频的一块区域然后进行裁剪。于是自定义了一个裁剪View,下面简单分享下心得:
目录
1.明确需求
ffmpeg执行视频裁剪的命令:
```"-y -i %s -filter_complex delogo=x=%s:y=%s:w=%s:h=%s:show=0 %s"```
x、y为裁剪矩形左上角的坐标,w、h为裁剪的宽度和高度。所以我要做的就是获取这几个参数。
自定义View的大致效果
为了便于表述,将上面的图形划分为几个区域。
功能:
1)拖动边角(A1-A4)时,View会在x、y两个方向伸缩。
2)拖动边(B1、B3)时,View会在x方向伸缩。
3)拖动边(B2、B4)时,View会在y方向伸缩。
4)拖动中心位置时,View会任意移动。
5)View在做伸缩和移动时,不可超出视频区域。
自定义View的适用范围
当前自定义View,适用于任何目标View的裁剪,本文章仅以视频裁剪为例。
2.代码设计
模型构建
由上面的效果图,可以将自定义View划分成3个部分:四个角、外框、网格线。
上代码
根据我的上篇博客https://www.jianshu.com/p/51fe99e9eb70的套路:
1)定义画笔
private Paint cornerPaint = new Paint(); //四个角绘制器
private Paint boundPaint = new Paint(); //外框绘制器
private Paint gridPaint = new Paint(); //格网绘制器
... //定义画笔的属性
2)定义一些参数变量
包括自定义View的宽高、画笔的宽度等
- 绑定目标裁剪View
绑定目标裁剪View,目的是为了CutView在layout时不会超越边界。
绑定的时机:要在目标裁剪View的onLayout方法执行之后,一般可以复写目标裁剪View的onLayout方法,通过一个监听来控制。
/**
* 绑定要裁切的View
* @param view
*/
public void bindTargetView(View view){
targetView = view;
this.maxWidth = view.getMeasuredWidth()+cornerStrokeWidth*2-2;
this.maxHeight = view.getMeasuredHeight()+cornerStrokeWidth*2-2;
this.limitBounds.left =view.getLeft() -cornerStrokeWidth+1;
this.limitBounds.top =view.getTop()-cornerStrokeWidth+1;
this.limitBounds.right = view.getLeft() + view.getMeasuredWidth() + cornerStrokeWidth-1;
this.limitBounds.bottom = view.getTop() + view.getMeasuredHeight() + cornerStrokeWidth-1;
Log.d(TAG, "bindTargetView: " + limitBounds );
}
- 计算目标裁剪View的缩放比
假如目标裁剪View是SurfaceView,为了防止视频显示变形,一般会对SurfaceView作一个缩放,这时候SurfaceView的尺寸就不是实际视频的尺寸了。但是ffmpeg命令传入的参数是视频的实际参数,所以下面的OnCutResultListener要回传CutView裁剪后尺寸乘以缩放比后实际的值。
3)作画
在onDraw方法中,绘制四个角、边框、格网。
4)动画
定义触摸模型:
enum TouchDownMode{
LEFT_SCALE, //左边界移动
RIGHT_SCALE, //右边界移动
TOP_SCALE, //上边界移动
BOTTOM_SCALE, //下边界移动
LEFT_TOP_CORNER_SCALE, //左上角移动
RIGHT_TOP_CORNER_SCALE, //右上角移动
LEFT_BOTTOM_CORNER_SCALE, //左下角移动
RIGHT_BOTTOM_CORNER_SCALE, //右下角移动
CENTER_MOVE, //中心任意拖动
UNKNOWN;
}
在onTouchEvent方法的MotionEvent.ACTION_DOWN方法里,根据x、y判断触摸区域,定义触摸模型。
在onTouchEvent方法的MotionEvent.ACTION_MOVE里,根据触摸模型和位移来重新layout当前View(也就是改变View在父布局中的位置,同时改变view的大小。)
5)添加监听
public interface OnCutResultListener{
void onCutResult(CutViewResultBean cutViewResultBean);
}
在View的onLayout方法里,将选择的区域参数通过监听回传。
注意:
3.如何使用这个View
1)定义一个RelativeLayout(假如名字是rlWrapper),宽高为wrap,add居中显示的目标裁剪View(就是要选择区域的View)。
2)动态的构造自定义View
cutView = new CutView(this);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(DensityUtil.dp2px(this, 100), DensityUtil.dp2px(this, 200));
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
cutView.setLayoutParams(layoutParams);
cutView.setOnCutResultListener(new CutView.OnCutResultListener() {
@Override
public void onCutResult(CutViewResultBean cutViewResultBean) {
curCutViewResultBean = cutViewResultBean;
AndroidConsoleLogPrinter.d(cutViewResultBean.toString());
}
});
然后添加到直接父布局中。
# 源码:
如有疑惑或者好的建议,或者想纠正作者的博文,请联系如下
公众号:微信公众号搜索“修符道人”或者扫描下方二维码
微信号:XinYi1349308479
QQ邮箱:1349308479@qq.com
0 条评论