最近在做一款视频剪辑的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 条评论

发表回复

您的电子邮箱地址不会被公开。