Android 九宫格视频展示

一、前言

一个有趣的现象,抖音上一度热传九宫格视频,其本质都是利用视频合成算法将视频原有视频编辑裁剪,最终展示处理。但实际还有更简单的方法,无需编辑视频的情况下,同样也可以实现九宫格展示。

二、实现原理

2.1 原理

做音视频开发也有一段时间了,在这个领域很多看似高大上的东西,实际上往往都有很多简便的方法去代替,从视频编辑到多屏投影,某些情况下并非一定要学习open gl才可以做到。

Android中提供了Path工具,其功能非常强大,很多不规则形状往往都需要Path实现,同样,本篇会利用Path进行镂空视频画布。

Path.Op 作为多个Path合成的重要操作符,其功能同样可以实现将Path闭合空间进行挖空的操作,目前主要有以下操作符。

vbnet 复制代码
Path.Op.DIFFERENCE          Path1调用合并函数:减去Path2后Path1区域剩下的部分
Path.Op.INTERSECT           保留Path2 和 Path1 共同的部分
Path.Op.UNION               保留Path1 和 Path 2
Path.Op.XOR                 保留Path1 和 Path2 + 共同的部分
Path.Op.REVERSE_DIFFERENCE  与 Path.Op.DIFFERENCE相反,减去Path1后Path2区域剩下的部分

今天我们主要用到Path.Op.DIFFERENCE ,原因是XOR 多次存在叠加问题,下图Path节点的地方,实际上正如XOR所述进行了叠加,因此这里使用XOR效果不符合预期。

2.2 核心代码

ini 复制代码
        float columWidth = clipRect.width() / col;  //每列的宽度
        float rowHeight = clipRect.height() / row;   //每行的高度


        for (int i = 1; i < col; i++) {
            tmpPath.reset();
            float position = i * columWidth - lineWidth/2;
            tmpPath.addRect(offsetLeft + position, offsetTop, offsetLeft + position + lineWidth / 2, height - offsetBottom, Path.Direction.CCW);
            clipPath.op(tmpPath, Path.Op.XOR);
        }
        for (int i = 1; i < row; i++) {
            tmpPath.reset();
            float position = i * rowHeight - lineWidth/2;
            tmpPath.addRect(offsetLeft , offsetTop + position, width - offsetRight, offsetTop + position + lineWidth / 2, Path.Direction.CCW);
            clipPath.op(tmpPath, Path.Op.XOR);
        }

尝试修改行列的效果

三、完整代码

ini 复制代码
public class GridFrameLayout extends FrameLayout {
    private Path clipPath;
    private Path tmpPath = new Path();
    private RectF clipRect;
    private Paint paint;
    //由于有的视频存在黑边,添加如下offset便于剔除黑边,保留纯画面区域
    private int offsetTop = 0;
    private int offsetBottom = 0;
    private int offsetRight = 0;
    private int offsetLeft = 0;
    private PaintFlagsDrawFilter mPaintFlagsDrawFilter;

    private int row = 3;  //行数
    private int col = 4; //列数

    private int lineWidth = 0;


    public GridFrameLayout(Context context) {
        super(context);
        init();
    }

    public GridFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public GridFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaintFlagsDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.WHITE);
        paint.setStrokeWidth(10);
        paint.setStyle(Paint.Style.STROKE);
        lineWidth = dpToPx(5);
    }

    public void setRow(int row) {
        this.row = row;
    }
    public void setColum(int col) {
        this.col = col;
    }
    public void setOffsetTop(int offsetTop) {
        this.offsetTop = offsetTop;
    }
    public void setOffsetBottom(int offsetBottom) {
        this.offsetBottom = offsetBottom;
    }

    public void setOffsetRight(int offsetRight) {
        this.offsetRight = offsetRight;
    }

    public void setOffsetLeft(int offsetLeft) {
        this.offsetLeft = offsetLeft;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        clipRect = null;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        int height = getHeight();
        int width = getWidth();
        DrawFilter drawFilter = canvas.getDrawFilter();

        int saveCount = canvas.save();
        //保存当前状态

        canvas.setDrawFilter(mPaintFlagsDrawFilter);
        if (clipPath == null) {
            clipPath = new Path();
        }
        if (clipRect == null) {
            clipRect = new RectF(offsetLeft, offsetTop, width - offsetRight, height - offsetBottom);
        } else {
            clipRect.set(offsetLeft, offsetTop, width - offsetRight, height - offsetBottom);
        }

        clipPath.reset();
        float radius = dpToPx(10);
        float[] radii = new float[]{
                radius, radius,
                radius, radius,
                radius, radius,
                radius, radius
        };

        clipPath.addRoundRect(clipRect, radii, Path.Direction.CCW);

        float columWidth = clipRect.width() / col;
        float rowHeight = clipRect.height() / row;

        for (int i = 1; i < col; i++) {
            tmpPath.reset();
            float position = i * columWidth - lineWidth / 2;
            tmpPath.addRect(offsetLeft + position, offsetTop, offsetLeft + position + lineWidth / 2, height - offsetBottom, Path.Direction.CCW);
            clipPath.op(tmpPath, Path.Op.DIFFERENCE);
        }
        for (int i = 1; i < row; i++) {
            tmpPath.reset();
            float position = i * rowHeight - lineWidth / 2;
            tmpPath.addRect(offsetLeft, offsetTop + position, width - offsetRight, offsetTop + position + lineWidth / 2, Path.Direction.CCW);
            clipPath.op(tmpPath, Path.Op.DIFFERENCE);
        }

        canvas.clipPath(clipPath);
        //裁剪画布,注意,这里不仅裁剪外围,内部挖空区域也会被裁剪
        //为什么在dispatchDraw中使用,因为dispatchDraw方便控制子View的绘制
        super.dispatchDraw(canvas);

        canvas.restoreToCount(saveCount);
        //恢复到之前的区域

        canvas.setDrawFilter(drawFilter);
        if (hasFocus()) {
            canvas.drawPath(clipPath, paint); //有焦点时画一个边框
        }
    }
    private int dpToPx(int dps) {
        return Math.round(getResources().getDisplayMetrics().density * dps);
    }
    public void setLineWidth(int lineWidth) {
        this.lineWidth = lineWidth;
    }
}

四、总结

Canvas 作为2D绘制常用的组件,其实有很高级功能,如Matrix、Camera、Shader、drawBitmapMesh等,正确的使用往往能带来事半功倍的效果,因此有必要通过不断的摸索才能发挥极致。

相关推荐
栈老师不回家10 分钟前
Vue 计算属性和监听器
前端·javascript·vue.js
前端啊龙16 分钟前
用vue3封装丶高仿element-plus里面的日期联级选择器,日期选择器
前端·javascript·vue.js
一颗松鼠20 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
小远yyds40 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
吕彬-前端2 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱2 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai2 小时前
uniapp
前端·javascript·vue.js·uni-app
bysking3 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓3 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4113 小时前
无网络安装ionic和运行
前端·npm