自定义View——Path的基本用法

摘自:https://github.com/GcsSloop/AndroidNote

一、Path的基本操作

作用:绘制复杂的图形,绘制复杂的文本和图形剪切

简介:Path是封装了由直线和曲线(二次,三次贝塞尔曲线)构成的几何路径。所以说需要明白直线和贝塞尔曲线的绘制。

第1组: moveTo、 setLastPoint、 lineTo 和 close(直线的绘制)

(1)moveTo()
方法:publicvoid moveTo (float x, float y)
作用:我们知道两点确定一条直线,moveTo()确定直线的起点在哪里。,需要和lineTo()一起使用。等会跟lineTo()一起演示
(2)lineTo()
方法:public void lineTo (float x, float y)
作用:确定直线的终点。
那么lineTo()的起点是怎么确定的呢:
①、首先如果没有使用moveTo()的情况下,则为cavnas画布的原点位置。
②、如果使用moveTo()的情况下,则为moveTo()指定的位置。
③、第二次lineTo()的起点,为第一次lineTo()的终点。
测试代码:

①、第一步:创建公用的画笔
<span style="font-size:18px;">public PathView(Context context) {
        super(context);
        init();
    }

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

    private void init(){
        //第一步:自定义画笔
        mPaintPath = new Paint();
        mPaintPath.setColor(Color.BLACK);
        mPaintPath.setStyle(Paint.Style.STROKE);
        mPaintPath.setStrokeWidth(5);
    }</span>

②、绘图
<span style="font-size:18px;">    //图一:直接使用lineTo()
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Path path = new Path();
        path.lineTo(200,200);
        //绘制路径
        canvas.drawPath(path,mPaintPath);
    }

    //图二:调用moveTo()再使用lineTo()
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Path path = new Path();
        path.moveTo(200,200);
        path.lineTo(200,400);
        canvas.drawPath(path,mPaintPath);
    }
</span>

记得修改activity_main.xml

图像:


③、setLastPoint
方法:public void setLastPoint(float x,float y)
作用:重置之前操作的最后一个点位置。也就是说之前lineTo()设置点终点被修改
测试:(我们在图三的代码上,加了setLastPoint())
 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Path path = new Path();
        path.moveTo(200,200);
        path.lineTo(200,400);
        //第二条线
        path.lineTo(400,200);
        //修改第二条线的终点
        path.setLastPoint(400,400);
        canvas.drawPath(path,mPaintPath);
    }
结果:



④、close()
作用:故名思意是用来闭合路径用的
测试:(我们也在图三上加上close()吧)
 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Path path = new Path();
        path.moveTo(200,200);
        path.lineTo(200,400);
        //第二条线
        path.lineTo(400,200);
        //闭合路径
        path.close();
        canvas.drawPath(path,mPaintPath);
    }
结果:

发现:连接终点与起点的直线。
注意:如果连接了终点和起点还是无法形成封闭图形的话,则close()什么也不做。


第二组: addXxx与arcTo
①、addXxx()
作用:同cavnas类中绘制各种各样的图形

方法预览:

// 第一类(基本形状)
    // 圆形
    public void addCircle (float x, float y, float radius, Path.Direction dir)
    // 椭圆
    public void addOval (RectF oval, Path.Direction dir)
    // 矩形
    public void addRect (float left, float top, float right, float bottom, Path.Direction dir)
    public void addRect (RectF rect, Path.Direction dir)
    // 圆角矩形
    public void addRoundRect (RectF rect, float[] radii, Path.Direction dir)
    public void addRoundRect (RectF rect, float rx, float ry, Path.Direction dir)
发现:跟cavnas的drawXxx()方法没什么大区别,就是多了一个Path.Direction
Path.Direction 是枚举类型:表示该图像是顺时针绘制还是逆时针绘制。
顺时针:CW             逆时针是:CCW
关于顺时针逆时针的作用需要之后才能解释。

②、了解Path.addPath()
作用:将两个路径合并。
方法:
// 第二类(Path)
    // path
    public void addPath (Path src)
    public void addPath (Path src, float dx, float dy)
    public void addPath (Path src, Matrix matrix)
测试:
 protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //将画布移到中心位置
        canvas.translate(mViewWidth/2,mViewHeight/2);
        //创建一个路径绘制正方形
        Path rectPath = new Path();
        rectPath.addRect(-200,-200,200,200,Path.Direction.CW);
        //创建一个路径绘制圆形
        Path circlePath = new Path();
        circlePath.addCircle(0,0,200, Path.Direction.CW);
        //合并路径,设置circlePath左上角的偏移量为0
        rectPath.addPath(circlePath,0,0);
        //绘制路径
        canvas.drawPath(rectPath,mPaintPath);
    }
结果:


③、了解:addArc()与arcTo()
作用:都是用来绘制圆弧的
// 第三类(addArc与arcTo)
    // addArc
    public void addArc (RectF oval, float startAngle, float sweepAngle)
    // arcTo
    public void arcTo (RectF oval, float startAngle, float sweepAngle)
    public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)

forceMoveTo是什么作用呢?

这个变量意思为“是否强制使用moveTo”,也就是说,是否使用moveTo将变量移动到圆弧的起点位移,也就意味着:

forceMoveTo含义等价方法
true不连接最后一个点与弧的起点,同addArc()方法public void addArc (RectF oval, float startAngle, float sweepAngle)
false而是连接最后一个点与圆弧起点,arcTo默认为falsepublic void arcTo (RectF oval, float startAngle, float sweepAngle)
测试:
 //图一:使用arcTo为true的情况
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //将画布移到中心位置
        canvas.translate(mViewWidth/2,mViewHeight/2);
        //创建一条直线
        Path path = new Path();
        path.moveTo(-200,0);
        path.lineTo(0,0);
        //创建一条圆弧
        RectF rect = new RectF(0,-200,200,0);
        path.arcTo(rect,270,180,true);
        //创建一条直线
        path.lineTo(100,-200);
        canvas.drawPath(path,mPaintPath);
    }

 //图二:使用addArc()
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //将画布移到中心位置
        canvas.translate(mViewWidth/2,mViewHeight/2);
        //创建一条直线
        Path path = new Path();
        path.moveTo(-200,0);
        path.lineTo(0,0);
        //创建一条圆弧
        RectF rect = new RectF(0,-200,200,0);
        path.addArc(rect,270,180);
        //创建一条直线
        path.lineTo(100,-200);
        canvas.drawPath(path,mPaintPath);
    }

  //图三:使用arcTo()设置foreMoveTo为false的情况
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //将画布移到中心位置
        canvas.translate(mViewWidth/2,mViewHeight/2);
        //创建一条直线
        Path path = new Path();
        path.moveTo(-200,0);
        path.lineTo(0,0);
        //创建一条圆弧
        RectF rect = new RectF(0,-200,200,0);
        path.arcTo(rect,270,180,false);
        //创建一条直线
        path.lineTo(100,-200);
        canvas.drawPath(path,mPaintPath);
    }
结果:
图一                                    图二                               图三



第3组:isEmpty、 isRect、 set 和 offset

①、isEmpty
作用:判断当前Path内容是否为空
②、isRect
作用:判断当前Path是否为一个矩形
③、set
作用:利用另一个Path重置当前Path
④、offset
作用:偏移当前Pah 的位置

方法预览:

        public void offset (float dx, float dy)
        public void offset (float dx, float dy, Path dst)

但是第二个方法最后怎么会有一个path作为参数?

其实第二个方法中最后的参数das是存储平移后的path的。

dst状态效果
dst不为空将当前path平移后的状态存入dst中,不会影响当前path
dat为空(null)平移将作用于当前path,相当于第一种方法


二、贝塞尔曲线原理
详情参照:贝塞尔曲线的原理
①、贝塞尔二阶曲线的使用
方法:public void qualTo(float controlX,float controlY,float endX,float endY);
使用:
Path path = new Path();
//曲线的起始点
path.moveTo(mStartPoint.x,mStartPoint.y);
//曲线的控制点和终止点
path.quadTo(mControlPoint.x,mControlPoint.y,mEndPoint.x,mEndPoint.y);
canvas.drawPath(path,mPaint);

②、贝塞尔三阶曲线的使用
方法:public void cubicTo(float x1,float y1,float x2,float y2,float x3,float y3);
使用:原理同上。

三、Path的进阶
①、rXxx()方法(rMoveTo, rLineTo, rQuadTo, rCubicTo)
作用:相对于上一点的坐标,进行偏移。
lineTo(float x, float y) 的终点是相对于Cavnas的原点作为坐标点,进行偏移的。
而rLineTo(float x,float y)的终点是相对于上一个点作为坐标点,进行平移的。
也就是说上一个点是(200,200) 然后调用rLineTo(200,200),则rLineTo()的实际值是(200+200,200+200)

②、图形的填充模式
1、电脑如何判断图形的内外
奇偶规则、非零环绕数规则:两种规则的原理
Android中的填充模式

Android中的填充模式有四种,是封装在Path中的一个枚举。

模式简介
EVEN_ODD奇偶规则
INVERSE_EVEN_ODD反奇偶规则
WINDING非零环绕数规则
INVERSE_WINDING反非零环绕数规则

我们可以看到上面有四种模式,分成两对,例如 "奇偶规则" 与 "反奇偶规则" 是一对,它们之间有什么关系呢?

Inverse 和含义是“相反,对立”,说明反奇偶规则刚好与奇偶规则相反,例如对于一个矩形而言,使用奇偶规则会填充矩形内部,而使用反奇偶规则会填充矩形外部,这个会在后面示例中代码展示两者对区别。

Android与填充模式相关的方法

这些都是Path中的方法。

方法作用
setFillType设置填充规则
getFillType获取当前填充规则
isInverseFillType判断是否是反向(INVERSE)规则
toggleInverseFillType切换填充规则(即原有规则与反向规则之间相互切换)

三、PathMeasure的使用
作用:用来测量Path的。
构造方法:
PathMeasure()创建一个空的PathMeasure
PathMeasure(Path path, boolean forceClosed)创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。
boolean foreClosed:表示测量是否是否使Path闭合(如果能够闭合的上的话)。
容易误解的地方:①、仅仅表示测量的时候闭合,之后进行一系列的测量计算。而不是将Path这个对象的图像闭合。
或者说PathMeasure假想path是闭合的图像,然后进行测量。而path本身是没有闭合的。
常用方法:
voidsetPath(Path path, boolean forceClosed)关联一个Path,作用跟构造方法2是相同的
booleanisClosed()检查path,是否闭合
floatgetLength()获取Path的长度,长度指的是周长。
   
booleangetSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)截取片段
原理:截取Path的周长,然后将截取到的长度添加到到dst中,最后决定截取的图片在dst的位置。如果不设置,默认为dst这个dst的最后一个点。
返回值(boolean)判断截取是否成功true 表示截取成功,结果存入dst中,false 截取失败,不会改变dst中内容
startD开始截取位置距离 Path 起点的长度取值范围: 0 <= startD < stopD <= Path总长度
stopD结束截取位置距离 Path 起点的长度取值范围: 0 <= startD < stopD <= Path总长度
dst截取的 Path 将会添加到 dst 中注意: 是添加,而不是替换
startWithMoveTo起始点是否使用 moveTo用于保证截取的 Path 起始点为原先位置的起始点。
示例:
//图一
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //将画布放到中间来
        canvas.translate(mViewWidth/2,mViewHeight/2);
        //创建一个圆形
        Path path1 = new Path();
        path1.addCircle(0,0,200, Path.Direction.CW);
        //创建一条直线
        Path path2 = new Path();
        path2.moveTo(-200,0);
        path2.lineTo(200,0);
        //绘制图
        canvas.drawPath(path1,mPaint);
        canvas.drawPath(path2,mPaint);
    }

//图二
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //将画布放到中间来
        canvas.translate(mViewWidth/2,mViewHeight/2);
        //创建一个圆形
        Path path1 = new Path();
        path1.addCircle(0,0,200, Path.Direction.CW);
        //创建一条直线
        Path path2 = new Path();
        path2.moveTo(-200,0);
        path2.lineTo(200,0);
        //获取一半的圆,加入到直线中。注意时针顺序问题
        PathMeasure measure = new PathMeasure(path1,false);
        measure.getSegment((float) Math.PI*200,(float) Math.PI*200*2,path2,true);
        canvas.drawPath(path2,mPaint);
    }



startWithMoveTo 为 false,则会将截取出来的 Path 片段的起始点移动到 dst 的最后一个点,以保证 dst 的连续性。这个例子看不出来。
booleannextContour()跳转到下一个轮廓
什么叫做调到下一轮廓

有这么一张图,如果使用PathMeasure测量Path,得到的是最外部的Path的周长。那么怎么获取内部Path的周长呢。
就需要用到nextContour()方法,调到内部去。

booleangetPosTan(float distance, float[] pos, float[] tan)获取指定长度的位置坐标及该点切线值
booleangetMatrix(float distance, Matrix matrix, int flags)获取指定长度的位置坐标及该点Matrix。可以说将方法一的数据放到了Matrix中
我现在有一个圆

我想获取红色点x,y的坐标点,和该位置想x,y的正切值(圆的趋势)。
我们就可以先获取该点在圆的周长上的位置,调用该方法放入数组中。
实例:
 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //将画布放到中间来
        canvas.translate(mViewWidth/2,mViewHeight/2);
        //创建一个圆形
        Path path1 = new Path();
        path1.addCircle(0,0,200, Path.Direction.CW);
        //获取圆1/2周长位置的值
        PathMeasure pathMeasure = new PathMeasure(path1,false);
        //创建获取数据的容器
        //pos 代表 x,y坐标
        float [] pos = new float[2];
        //tan 代表 当前
        float [] tan = new float[2];
        //调用方法获取数据
        pathMeasure.getPosTan((float)Math.PI*200,pos,tan);
        Log.d("PathMeasure","pos"+pos[0]+"  "+pos[1]+"  tan"+tan[0]+"  "+tan[1]);
    }





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值