Path 图形与逻辑运算

发布于 2025-06-09 11:12:35 字数 9992 浏览 1 评论 0

涉及知识

Path

类型API描述
添加路径addArc, addCircle, addOval, addPath, addRect, addRoundRect, arcTo依次为添加圆弧、圆、椭圆、路径、矩形、圆角矩形、圆弧
移动起点moveTo移动起点位置,仅对之后路径产生影响
移动终点setLastPoint移动上一次的终点位置,对前后的路径都会产生影响
直线lineTo增加一条道指定点的直线
贝塞尔quadTo, cubicTo二阶、三阶贝塞尔曲线
闭合路径close路径终点连接到起点
逻辑运算opA\B(DIFFERENCE), A∩B(INTERSECT), B\A(REVERSE_DIFFERENCE), A∪B(UNION), A⊕B(XOR)
替换路径set用新的路径替换当前路径
重置reset, rewind清除 path 使它为空,清除 path 但保留内部的数据结构
计算边界computeBounds计算路径的矩形边界
闭合方向Direction顺时针方向闭合 Path(CW),逆时针方向闭合 Path(CCW)

本来这章应该是 PieChart 的实战,可是我在编写的时候发现了一个设置背景图片的 bug。作为一个强迫症(ಥ _ ಥ),我只好引入了 Path 来解决这个 bug,所以就有了这一篇内容。

一、什么是 Path

官方描述:
Path class 封装了由直线、二次、三次贝塞尔曲线构成的多重曲线几何路径。它可以用 canvas.drawPath(path,paint) 方法绘图,填充和线都可以(根据 paint 的样式),或者它可以用于在绘图路径上裁剪或者绘出文本。

我的理解:
Path 由任意多条直线、二次贝塞尔或三次贝塞尔曲线组成,可以选择填充或者描边模式,可以使用它裁剪画布或者绘制文字。

二、添加路径

1、lineTo,moveTo

在之前的文章中,使用 canvas 的函数绘制过 坐标系 ,这次使用 path 来绘制。

a、创建画笔

创建画笔并初始化

//创建画笔
private Paint mPaint = new Paint();

private void initPaint(){
    //初始化画笔
    mPaint.setStyle(Paint.Style.FILL);//设置画笔类型
    mPaint.setAntiAlias(true);//抗锯齿
}

b、绘制坐标轴

使用 onSizeChanged 方法,获取根据父布局等因素确认的 View 宽高

//宽高
private int mWidth;
private int mHeight;

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

把原点从左上角移动到画布中心,绘制原点与四个端点

private Path mPath = new Path();

canvas.translate(mWidth/2,mHeight/2);// 将画布坐标原点移动到中心位置
//绘制坐标原点
mPaint.setColor(Color.BLACK);//设置画笔颜色
mPaint.setStrokeWidth(10);//为了看得清楚,设置了较大的画笔宽度
canvas.drawPoint(0,0,mPaint);
//绘制坐标轴 4 个断点
canvas.drawPoints(new float[]{
        mWidth/2*0.8f,0
        ,0,mHeight/2*0.8f
        ,-mWidth/2*0.8f,0
        ,0,-mHeight/2*0.8f},mPaint);

增加坐标轴与箭头的 Path,在完成后使用 canvas.drawPath 一次进行绘制

mPaint.setStrokeWidth(1);//恢复画笔默认宽度
//x 轴
mPath.moveTo(-mWidth/2*0.8f,0);//移动 path 起点到(-mWidth/2*0.8f,0)
mPath.lineTo(mWidth/2*0.8f,0);//直线终点为(mWidth/2*0.8f,0)
//y 轴
mPath.moveTo(0,-mHeight/2*0.8f);//移动 path 起点到(0,-mHeight/2*0.8f)
mPath.lineTo(0,mHeight/2*0.8f);//直线终点为(0,mHeight/2*0.8f)
//x 箭头
mPath.moveTo(mWidth/2*0.8f*0.95f,-mWidth/2*0.8f*0.05f);
mPath.lineTo(mWidth/2*0.8f,0);
mPath.lineTo(mWidth/2*0.8f*0.95f,mWidth/2*0.8f*0.05f);
//y 箭头
mPath.moveTo(mWidth/2*0.8f*0.05f,mHeight/2*0.8f-mWidth/2*0.8f*0.05f);
mPath.lineTo(0,mHeight/2*0.8f);
mPath.lineTo(-mWidth/2*0.8f*0.05f,mHeight/2*0.8f-mWidth/2*0.8f*0.05f);
//绘制 Path
canvas.drawPath(mPath,mPaint);

坐标系
可以看出 moveTo 方法,可以移动下一次增加 path 的起点,而 lineTo 中的参数,即为直线的终点。

2、addArc 与 arcTo

方法区别
addArc画一段圆弧
arcTo画一段圆弧,当上一次的终点与圆弧起点未连接时,可以设置是否连接这两点

addArc

r = Math.min(mWidth,mHeight)*0.6f/2;
mRectF.left = 0;
mRectF.top = -r;
mRectF.right = r;
mRectF.bottom = 0;
mPath.addArc(mRectF,-60,180);
//绘制 Path
canvas.drawPath(mPath,mPaint);

addArc
再来看看 arcTo

//arcTo
mPath.moveTo(0,0);
mPath.arcTo(mRectF,-60,180);
//绘制 Path
canvas.drawPath(mPath,mPaint);

arcTo
可以看到 arcTo 多了 一条从原点到圆弧起点的直线 ,而如果设置为 mPath.arcTo(mRectF,-60,180,false);效果将和 addArc 相同。

三、圆角图片以及更多形状图片

继承 ImageView ,重写父类的 onSizeChanged 方法,获取 View 尺寸,之后根据 View 大小对图片进行压缩。

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mViewWidth = w;
    mViewHeight = h;
    size();//切割尺寸计算
    scaleBitmap();//压缩图片尺寸函数
}

onDraw 方法中进行样式绘制,在其中使用 clipPath 的方法来实现圆角图片。

@Override
protected void onDraw(Canvas canvas) {
    canvas.translate(mViewWidth/2,mViewHeight/2);//将画布坐标原点移动到中心位置
    canvas.clipPath(pathFigure(), Region.Op.INTERSECT);//切割
    mPath.reset();
    canvas.drawBitmap(b,rect,rect,mPaint);
}

scaleBitmap 方法中对图片的尺寸进行压缩

private void scaleBitmap(){
    Drawable drawable = getDrawable();//获取图片
    if (drawable == null) {
        return;
    }
    if (getWidth() == 0 || getHeight() == 0) {
        return;
    }
    if (!(drawable instanceof BitmapDrawable)) {
        return;
    }
    b = ((BitmapDrawable) drawable).getBitmap();//获取 bitmap
    if (null == b) {
        return;
    }
    float scaleWidth = (float) length/b.getWidth();
    float scaleHeight = (float) length/b.getHeight();
    matrix.postScale(scaleWidth,scaleHeight);//缩放矩阵
    b=Bitmap.createBitmap(b,0,0,b.getWidth(),b.getHeight(),matrix,true);//压缩图片
}

size 方法中设置 canvas 的切割尺寸

protected void size(){
    length = Math.min(mViewWidth,mViewHeight)/2;
    rect = new Rect(-(int) length, -(int) length, (int) length, (int) length);//绘制图片矩阵
}

现在就是发挥想象力的时候啦♪(^∇^*),来编写 pathFigure() 方法吧

a、先编写一个简单的圆形图片样式

protected Path pathFigure(){
    switch (modeFlag){
        case CIRCLE:
            mPath.addCircle(0,0,length, Path.Direction.CW);//增加圆的 path,顺时针闭合圆
            break;
    }
    return mPath;
}

圆形

b、增加一个圆角图片样式


private RectF rectF = new RectF();

case ROUNDRECT:
            rectF.left = -length;
            rectF.top = -length;
            rectF.right = length;
            rectF.bottom = length;
            //圆角矩形,radius 为圆角的半径,顺时针闭合圆角矩形
            mPath.addRoundRect(rectF,radius,radius, Path.Direction.CW);
            break;

圆角

c、再增加一个扇形样式

( PS:为了可以获得更多的图片面积,需要把圆心下移一个 length 的距离,半径扩大到之前的两倍 )

case SECTOR:
    rectF.left = -length*2;
    rectF.top = -length;
    rectF.right = length*2;
    rectF.bottom = length*3;
    mPath.moveTo(0,length);
    mPath.arcTo(rectF,angle,-angle*2-180);//绘制圆弧
    break;

扇形

四、逻辑运算

两条 Path 可通过多种逻辑运算进行结合,形成新的 Path。

API 如下:

op(Path path, Path.Op op)
op(Path path1, Path path2, Path.Op op)

逻辑运算具有五种类型:

方法描述示意图
DIFFERENCEB 在 A 中的相对补集,即 A 减去 A 与 B 的交集DIFFERENCE
REVERSE_DIFFERENCEA 在 B 中的相对补集合,即 B 减去 B 与 A 的交集REVERSE_DIFFERENCE
INTERSECTA 与 B 的交集INTERSECT
UNIONA 与 B 的合集UNION
XORA 与 B 的合集减去 A 与 B 的交集XOR

使用 Path.op 方法再给圆角图片类,增加一种环形样式:

case RING:
    rectF.left = -length*2;
    rectF.top = -length;
    rectF.right = length*2;
    rectF.bottom = length*3;
    mPath1.moveTo(0,length);
    mPath1.arcTo(rectF,angle,-angle*2-180);//较大的圆弧

    rectF.left = -length/2;
    rectF.top = length/2;
    rectF.right = length/2;
    rectF.bottom = length*3/2;
    mPath2.moveTo(0,length);
    mPath2.arcTo(rectF,angle,-angle*2-180);//较小的圆弧

    mPath.op(mPath1,mPath2, Path.Op.XOR);//异或获取环形

圆环

五、小结

本文介绍了 Path 的基本使用方法与逻辑运算,同时通过圆角图片的例子,进行了实战。使用 Path 方法,还可以增加更多有趣的图形,比如 star,多边形,格子图等等。如果在阅读过程中,有任何疑问与问题,欢迎与我联系。

示例中使用的方法,相对消耗内存,更合适的是设置反向填充来完成圆角图片的生成,FigureImageView 为反向填充的方法,OldFigureImageView 为示例中的方法 源码

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。