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等,正确的使用往往能带来事半功倍的效果,因此有必要通过不断的摸索才能发挥极致。

相关推荐
weixin199701080168 分钟前
《电子元器件商品详情页前端性能优化实战》
前端·性能优化
Southern Wind9 分钟前
Vue 3 + Naive UI 企业级后台管理系统完整解析
前端·vue.js·ui·typescript
清汤饺子10 分钟前
AI 编程新范式:Spec First 的四件套,让 AI 不再是"热情但跑偏的实习生"
前端·javascript·后端
weixin1997010801610 分钟前
《建材网商品详情页前端性能优化实战》
前端·性能优化
程序员 沐阳15 分钟前
从缓慢等待到瞬间响应:Vite 如何重塑前端开发体验
前端·前端框架
wangjinsheng59317 分钟前
Vue3 + Element Plus 前端 AI 编码模板
前端·vue.js·ai·elementui·ai编程
roman_日积跬步-终至千里20 分钟前
【后端】Spring Boot Web请求核心问题解析
前端·spring boot·后端·系统架构
Mintopia25 分钟前
让开发效率翻倍的,往往不是新技术,而是小工具
前端
Cobyte27 分钟前
2.响应式系统基础:依赖追踪的基础 —— 发布订阅模式(前端应用最广的设计模式)
前端·javascript·vue.js
慧一居士30 分钟前
Nitro 和nuxt4框架关系
前端