Path基础用法

Path

路径,相当于是将画笔绘制的轨迹的一个集合

    我曾经觉得path 就是随意的画画轨迹就完事了,至于贝塞尔曲线!!!? 一直听说,怎么玩的 不太清楚.... 或者 知道贝塞尔,就是path.c..,怎么应用在实际的工作中? 不会。所以有了今天的学习,今天的目标主要是了解path,了解贝塞尔怎么用,并且实际的撸一下代码,记录下笔记。

path中的基础用法


moveTo 移动

lineTo   连接线

close 封闭

简单绘制图形  

//最后的参数表示绘制的方向  CW顺时针 CCW逆时针 

addArc() 添加弧线

addOval() 添加椭圆

addRect() 添加矩形

addRoundRect() 添加圆角矩形

addCircle() 添加圆形

//让你看看 顺逆时针到底有什么卵用

path.addCircle(500,500,500, Path.Direction.CCW);

canvas.drawPath(path,mPaint);

mPaint.setTextSize(30);

mPaint.setStrokeWidth(1);

canvas.drawTextOnPath("我是这么玩的",path,0,0,mPaint);


Path基础用法_第1张图片
CWW逆时针  CW顺时针

在这里 我觉得这个圆角矩形是有点意思的,之前也确实没注意过这个

//1.四角一样弧度

RectF rectF=new RectF(100,100,600,400);

path.addRoundRect(rectF,50,50, Path.Direction.CCW);

canvas.drawPath(path,mPaint);

//2.单独设定每个角的弧度 第二参每两个值表示 左上x,y 右上x,y 右下x,y 左下x,y

canvas.save();

canvas.translate(0,400);

path.reset();

path.addRoundRect(rectF,new float[]{150,150,50,50,50,50,0,0}, Path.Direction.CCW);

canvas.drawPath(path,mPaint);

canvas.restore();

Path基础用法_第2张图片
咳,图片这么大,也就将就下看吧

这里有点我感觉重复的东西, 就是 op属性 ,当两个path组合时,使用op属性 可以实现一些特殊的效果。特殊? 其实和paint的mode差不多,就是又简单一点的几个效果而已

以下内容转自https://blog.csdn.net/xifei66/article/details/108831640

op属性/布尔值运算


Path基础用法_第3张图片
一个简单的例子,就是画个太极鱼

    感觉这个基础用法其实就够了,如果还想了解的话,可以看看 更多的用法,哈哈,只是我一个小渣渣的感觉

接下来是更多的用法,至于有没有用,你可以看看,也许会用到。

r标识


path.rLineTo();

path.rCubicTo();

path.rMoveTo();

path.rQuadTo();

都是在原有基础上,再相对偏移一定量。

arcTo


什么玩意儿...其实就是绘制圆弧。那么i和addArc有什么区别?

为了更明显的看出一些效果,我绘制了个坐标系,再绘制个圆弧的外接矩形,这块可以不考虑绘制,不影响

//先画个x-y坐标系

mPaint.setStrokeWidth(0);

mPaint.setTextSize(40);

mPaint.setColor(Color.DKGRAY);

//x轴

canvas.drawLine(50,350,800,350,mPaint);

canvas.drawText("x",0,1,800,350,mPaint);

//y轴

canvas.drawLine(350,50,350,800,mPaint);

canvas.drawText("y",0,1,350,800,mPaint);

//画个矩形框

RectF rectF=new RectF(100,100,600,600);

mPaint.setStrokeWidth(2);

mPaint.setColor(Color.RED);

mPaint.setPathEffect(new DashPathEffect(new float[]{100,20},0));

canvas.drawRect(rectF,mPaint);

mPaint.setPathEffect(null);

//想判断后续的区别必须把path赋个值

path.lineTo(100,100);

//绘制圆弧    

addArc和arcTo都是加入圆弧到path中。

addArc是直接加入圆弧到path中

arcTo会根据forceMoveTo属性 (true 强制移动    false 不强制移动)  默认为false

false时推断要绘制圆弧的起点与绘制圆弧之前path中最后的点是否是同一个点,假设不是同一个点的话,就会连接两个点。

 1)addArc

//25表示偏移 具体值是绘制时的画笔宽度的一半,要不然会出现绘制的图一半宽度在外界的现象

RectF rectFInner=new RectF(100+25,100+25,600-25,600-25);

//由图很明显可看出 从3点钟方向绘制

path.addArc(rectFInner,0,130);

mPaint.setStrokeWidth(50);

mPaint.setColor(Color.BLUE);

canvas.drawPath(path,mPaint);

2)arcTo

path.arcTo(rectFInner,0,130,false);

计算面积


computeBounds (RectF bounds, boolean exact)    

bounds:测量结果会放入这个矩形

exact:是否精确测量  目前这一个参数作用已经废弃,一般写true即可

RectF tempRect=new RectF();

path.computeBounds(tempRect,true);

mPaint.setStyle(Paint.Style.STROKE);

canvas.drawRect(tempRect,mPaint);

小兄弟,这里就把基本内容聊完了,我们开始精彩的贝塞尔阶段吧。

//二阶

path.moveTo(100,100);    //起始点

path.quadTo(300,700,200,100);  //控制点  结束点

canvas.drawCircle(100,100,10,mPaint);

canvas.drawCircle(300,700,5,mPaint);

canvas.drawCircle(200,100,10,mPaint);

canvas.drawPath(path,mPaint);

//三阶

path.moveTo(100,900);  //起始点

path.cubicTo(300,200,400,400,600,900); //控制点1  控制点2  结束点

canvas.drawCircle(100,900,10,mPaint);

canvas.drawCircle(300,200,5,mPaint);

canvas.drawCircle(400,400,5,mPaint);

canvas.drawCircle(600,900,10,mPaint);

canvas.drawPath(path,mPaint);

完了。  完了!!!? 这是什么鬼....有个毛用.

其实上面的话,是我一直以来的感觉,基于当前的公司需求,连动画效果都完全不需要。所以我对这块的了解基本为0。

先胡乱的上传两个跟着课程敲得有关贝塞尔的例子

1.不断递归1阶贝塞尔公式,实现的N阶贝塞尔

//蔑视 贝塞尔

public class DespiseBezierextends View {

//点

    private PaintmPaint;

//线

    private PaintmLinePointPaint;

//路径

    private PathmPath;

//几阶贝塞尔

    private int order=2;

//点集

    private ArrayListpointList=new ArrayList<>();

public DespiseBezier(Context context) {

this(context,null);

}

public DespiseBezier(Context context,@Nullable AttributeSet attrs) {

this(context, attrs,0);

}

public DespiseBezier(Context context,@Nullable AttributeSet attrs,int defStyleAttr) {

this(context, attrs, defStyleAttr,0);

}

public DespiseBezier(Context context,@Nullable AttributeSet attrs,int defStyleAttr,int defStyleRes) {

super(context, attrs, defStyleAttr, defStyleRes);

init();

}

//初始化

    private void init(){

mPaint =new Paint();

mPaint.setAntiAlias(true);

mPaint.setStrokeWidth(4);

mPaint.setTextSize(26);

mPaint.setStyle(Paint.Style.STROKE);

mPaint.setColor(Color.RED);

mLinePointPaint =new Paint();

mLinePointPaint.setAntiAlias(true);

mLinePointPaint.setStrokeWidth(4);

mLinePointPaint.setStyle(Paint.Style.STROKE);

mLinePointPaint.setColor(Color.GRAY);

mPath =new Path();

}

@Override

    protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

if (pointList.size()==0) {

return;

}

PointF pointF=new PointF();

for (int i =0; i <=order; i++) {

pointF =pointList.get(i);

if (i>0){

mLinePointPaint.setColor(Color.GRAY);

canvas.drawLine(pointList.get(i-1).x,pointList.get(i-1).y,pointF.x,pointF.y,mLinePointPaint);

}

//起点、终点换颜色

            if (i ==0) {

mLinePointPaint.setColor(Color.RED);

}else if (i ==order) {

mLinePointPaint.setColor(Color.BLUE);

}

canvas.drawCircle(pointF.x,pointF.y,5,mLinePointPaint);

canvas.drawText("("+(int)pointF.x+","+(int)pointF.y+")",pointF.x-1,pointF.y-10,mPaint);

}

createBezierPoint();

canvas.drawPath(mPath,mPaint);

}

@Override

    public boolean onTouchEvent(MotionEvent event) {

if (event.getAction()== MotionEvent.ACTION_DOWN) {

createRandomPoint();

invalidate();

}

return super.onTouchEvent(event);

}

//生成随机点

    private void createRandomPoint(){

Random random=new Random();

pointList.clear();

//  arrayList.add(new PointF(200,200));

        for (int i =0; i <=order; i++) {

PointF pointF=new PointF();

int x=random.nextInt(800)+200;

int y=random.nextInt(1000)+200;

pointF.x=x;

pointF.y=y;

pointList.add(pointF);

}

}

//生成贝塞尔点

    private void createBezierPoint(){

mPath.reset();

ArrayList points =new ArrayList<>();

//份数

        float delta =1.0f/1000;

for (float t =0; t <=1; t += delta) {

//bezier点集

            PointF pointF =new PointF(deCastelJau(order,0, t,true), deCastelJau(order,0, t,false));//计算在曲线上点位置

            points.add(pointF);

if (points.size() ==1) {

mPath.moveTo(points.get(0).x, points.get(0).y);

}else {

mPath.lineTo(pointF.x, pointF.y);

}

}

}

//阶层,  第几份,是否横坐标

    private float deCastelJau(int i,int j,float t,boolean calculateX) {

if (i ==1) {

return calculateX ? (1 - t) *pointList.get(j).x + t *pointList.get(j +1).x :

(1 - t) *pointList.get(j).y + t *pointList.get(j +1).y;

}else {

return (1 - t) * deCastelJau(i -1, j, t, calculateX) + t * deCastelJau(i -1, j +1, t, calculateX);

}

}

//外界传入order

    public void setOrder(int order){

this.order=order;

createRandomPoint();

invalidate();

}

}

2.QQ红色消息 贝塞尔效果


Path基础用法_第4张图片

public class QQBezierViewextends View {

//圆球画笔

    private PaintmBallPaint;

//曲线画笔

    private PaintmLinePaint;

//数字画笔

    private PaintmTextPaint;

//路径

    private PathmPath;

//设置移动球 球心

    private PointFmoveBallCenterP;

//设置固定球 球心

    private PointFstillBallCenterP;

//默认球半径

    private float moveBallRadius;

//小球半径

    private float stillBallRadius;

//移动的距离

    private float mDistance;

//连线的最远距离

    private float mLineMaxDistance;

//偏移量

    private float offset;

/**

* 气泡默认状态--静止

*/

    private final int BUBBLE_STATE_DEFAUL =0;

/**

* 气泡相连

*/

    private final int BUBBLE_STATE_CONNECT =1;

/**

* 气泡分离

*/

    private final int BUBBLE_STATE_APART =2;

/**

* 气泡消失

*/

    private final int BUBBLE_STATE_DISMISS =3;

/*

*标记当前状态

*/

    private  int state=BUBBLE_STATE_DEFAUL;

/*

* 消失部分

* */

//我认为多余的爆炸画笔

    private PaintmBurstPaint;

//爆炸绘制区域

    private RectmBurstRect;

/**

*  气泡爆炸的bitmap数组

*/

    private Bitmap[]mBurstBitmapsArray;

/**

* 是否在执行气泡爆炸动画

*/

    private boolean mIsBurstAnimStart =false;

/**

* 当前气泡爆炸图片index

*/

    private int mCurDrawableIndex;

/**

*  气泡爆炸的图片id数组

*/

    private int[]mBurstDrawablesArray = {R.drawable.burst_1, R.drawable.burst_2

            , R.drawable.burst_3, R.drawable.burst_4, R.drawable.burst_5};

public QQBezierView(Context context) {

this(context,null);

}

public QQBezierView(Context context,@Nullable AttributeSet attrs) {

this(context, attrs,0);

}

public QQBezierView(Context context,@Nullable AttributeSet attrs,int defStyleAttr) {

this(context, attrs, defStyleAttr,0);

}

public QQBezierView(Context context,@Nullable AttributeSet attrs,int defStyleAttr,int defStyleRes) {

super(context, attrs, defStyleAttr, defStyleRes);

init();

}

private void init(){

//setLayerType(LAYER_TYPE_SOFTWARE,null);

/*

*球画笔初始化

*/

        mBallPaint =new Paint();

mBallPaint.setAntiAlias(true);

mBallPaint.setDither(true);

// mBallPaint.setStrokeWidth(20);

        mBallPaint.setColor(Color.RED);

mBallPaint.setStyle(Paint.Style.FILL_AND_STROKE);

/*

* 数字画笔

* */

        mTextPaint =new Paint(Paint.ANTI_ALIAS_FLAG);//第二种写法

//    mTextPaint.setStrokeWidth(1);

        mTextPaint.setColor(Color.WHITE);

//  mTextPaint.setTextSize(25);

        mTextPaint.setStyle(Paint.Style.STROKE);

/*

* 曲线画笔

* */

        mLinePaint =new Paint(Paint.ANTI_ALIAS_FLAG);

mLinePaint.setStyle(Paint.Style.FILL_AND_STROKE);

mLinePaint.setColor(Color.RED);

//    mLinePaint.setStrokeWidth(10);

/*

* 贝塞尔点

* */

        mPath=new Path();

/*

* 爆炸数组准备

* */

        mBurstBitmapsArray=new Bitmap[mBurstDrawablesArray.length];

for (int i =0; i

mBurstBitmapsArray[i]= BitmapFactory.decodeResource(getResources(),mBurstDrawablesArray[i]);

}

mBurstRect=new Rect();

mBurstPaint=new Paint(Paint.ANTI_ALIAS_FLAG);

//解决view加载bitmap时的锯齿问题  实际上我没发现。。。。

        mBurstPaint.setFilterBitmap(true);

}

@Override

    protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

@Override

    protected void onSizeChanged(int w,int h,int oldw,int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

initData(0,0);

}

@Override

    protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.translate(getWidth()/2,getHeight()/2);

//1.绘制初始位置

      if (state!=BUBBLE_STATE_DISMISS){

canvas.drawCircle(moveBallCenterP.x,moveBallCenterP.y,moveBallRadius,mBallPaint);

//要解决数字被遮挡,应该将以下代码 放在下面的条件 后面

          Rect textRect=new Rect();

String text="100";

mTextPaint.getTextBounds(text,0,text.length(),textRect);

canvas.drawText(text,moveBallCenterP.x-mTextPaint.measureText(text)/2,moveBallCenterP.y+textRect.height()/2,mTextPaint);

}

//2.根据拖拽

      if (state==BUBBLE_STATE_CONNECT){

//绘制固定圆

          canvas.drawCircle(stillBallCenterP.x,stillBallCenterP.y,stillBallRadius,mBallPaint);

//绘制贝塞尔线

//1.计算角度

          float distance=(float) Math.hypot(moveBallCenterP.x-stillBallCenterP.x,moveBallCenterP.y-stillBallCenterP.y);

mPath.reset();

//获取控制点

          float mAnthorX=(moveBallCenterP.x+stillBallCenterP.x)/2;

float mAnthorY=(moveBallCenterP.y+stillBallCenterP.y)/2;

//获取角度

          float cosIn=(moveBallCenterP.x-stillBallCenterP.x)/distance;

float sinIn=(moveBallCenterP.y-stillBallCenterP.y)/distance;

//获取四点

//A

          float Ax=stillBallCenterP.x+stillBallRadius*sinIn;

float Ay=stillBallCenterP.y-stillBallRadius*cosIn;

//B

            float Bx=moveBallCenterP.x+moveBallRadius*sinIn;

float By=moveBallCenterP.y-moveBallRadius*cosIn;

//C

            float Cx=moveBallCenterP.x-moveBallRadius*sinIn;

float Cy=moveBallCenterP.y+moveBallRadius*cosIn;

//D

            float Dx=stillBallCenterP.x-stillBallRadius*sinIn;

float Dy=stillBallCenterP.y+stillBallRadius*cosIn;

mPath.moveTo(Ax,Ay);

mPath.quadTo(mAnthorX,mAnthorY,Bx,By);

mPath.lineTo(Cx,Cy);

mPath.quadTo(mAnthorX,mAnthorY,Dx,Dy);

mPath.close();

canvas.drawPath(mPath,mLinePaint);

}

//3.爆炸消失

        if (state==BUBBLE_STATE_DISMISS){

if (mIsBurstAnimStart){

mBurstRect.set((int)(moveBallCenterP.x-2*moveBallRadius),

(int)(moveBallCenterP.y-2*moveBallRadius),

(int)(moveBallCenterP.x+2*moveBallRadius),

(int)(moveBallCenterP.y+2*moveBallRadius));

canvas.drawBitmap(mBurstBitmapsArray[mCurDrawableIndex],null,mBurstRect,mBurstPaint);

}

}

}

@Override

    public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()){

case MotionEvent.ACTION_DOWN:

//测距

                mDistance=(float) Math.hypot(event.getX()-getWidth()/2-stillBallCenterP.x,event.getY()-stillBallCenterP.y-getHeight()/2);

//距离在这个范围内 改变它的状态

                if (mDistance

state=BUBBLE_STATE_CONNECT;

}else{

state=BUBBLE_STATE_DEFAUL;

}

break;

case MotionEvent.ACTION_MOVE:

//不是初始状态 (相连状态  分离状态)

                if (state!=BUBBLE_STATE_DEFAUL){

//赋值移动球的圆心

                    moveBallCenterP.set(event.getX()-getWidth()/2,event.getY()-getHeight()/2);

//获取距离 根据距离进行判断 是否变为分离状态,相连状态时,不断变化固定球的半径实现一个好像被不断抽取的变化效果

                    mDistance=(float) Math.hypot(event.getX()-getWidth()/2-stillBallCenterP.x,event.getY()-stillBallCenterP.y-getHeight()/2);

//这个范围内 不断变化固定球半径

                    if(mDistance

stillBallRadius=moveBallRadius-mDistance/8;

}else{

state=BUBBLE_STATE_APART;

}

}

invalidate();

break;

case MotionEvent.ACTION_UP:

//松开时 若之前是相连状态 则恢复成初始状态 若之前是分离状态 则改为消失状态

                if (state==BUBBLE_STATE_CONNECT){

startRestAnim();

}

if (state==BUBBLE_STATE_APART){

if(mDistance<2*moveBallRadius){

startRestAnim();

}else{

startDismissAnim();

}

}

break;

}

return true;

}

/*

* 初始化数据

* */

    private void initData(int w,int h){

if (moveBallCenterP==null) {

moveBallCenterP=new PointF(w/2,h/2);

}else{

moveBallCenterP.set(w/2,h/2);

}

if (stillBallCenterP==null){

stillBallCenterP=new PointF(w/2,h/2);

}else {

stillBallCenterP.set(w/2,h/2);

}

moveBallRadius=40;

stillBallRadius=moveBallRadius;

mLineMaxDistance=8*moveBallRadius;

offset=2*moveBallRadius;

//赋值初始状态

        state=BUBBLE_STATE_DEFAUL;

}

/*

* 传说中的属性动画  重置

* */

    private void startRestAnim(){

ValueAnimator valueAnimator=ValueAnimator.ofObject(new PointFEvaluator(),

new PointF(moveBallCenterP.x,moveBallCenterP.y),new PointF(stillBallCenterP.x,stillBallCenterP.y));

valueAnimator.setDuration(1000);

valueAnimator.setInterpolator(new OvershootInterpolator(5f));

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

            public void onAnimationUpdate(ValueAnimator animation) {

moveBallCenterP=(PointF) animation.getAnimatedValue();

invalidate();

}

});

valueAnimator.addListener(new AnimatorListenerAdapter() {

@Override

                                      public void onAnimationEnd(Animator animation) {

state=BUBBLE_STATE_DEFAUL;

}

});

valueAnimator.start();

}

/*

* 消失动画

* */

    private void startDismissAnim(){

mIsBurstAnimStart=true;

state=BUBBLE_STATE_DISMISS;

ValueAnimator animator=ValueAnimator.ofInt(0,mBurstBitmapsArray.length);

animator.setDuration(1000);

animator.setInterpolator(new LinearInterpolator());

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

            public void onAnimationUpdate(ValueAnimator animation) {

mCurDrawableIndex=(int)animation.getAnimatedValue();

invalidate();

}

});

animator.addListener(new AnimatorListenerAdapter() {

@Override

            public void onAnimationEnd(Animator animation) {

mIsBurstAnimStart=false;

}

});

animator.start();

}

}

跟着一点点敲出来,觉得收获很大,有种自豪感油然而生。要说具体会了什么,可能也并不会啥,但就是盲目自信了。哈哈哈

最近写的所有的自定义控件,都缺少两块比较重要的东西,1.xml可配置属性 2.measure部分。

1.实现xml可配置

1)values中建attrs文件夹

2)建 declare-styleable

3)xml中使用

4)view中获取

attrs

       

       

       

       

       

   

xml

    android:layout_width="500dp"

    android:layout_height="wrap_content"

    app:ballColor="@color/colorAccent"

    app:stillBallRadius="30px"

    app:text="200"

    app:textColor="#ffffff"

    app:textSize="24sp" />

view

        TypedArray type=context.obtainStyledAttributes(attrs,R.styleable.QQBezierView,defStyleAttr,defStyleRes);

        type.getColor(R.styleable.QQBezierView_ballColor,Color.RED);

        type.getColor(R.styleable.QQBezierView_textColor,Color.WHITE);

        type.getDimension(R.styleable.QQBezierView_textSize,25);

        type.getDimension(R.styleable.QQBezierView_stillBallRadius,40);

2.measure部分

这部分我还在验证,也许是不对的 需要改。--用是可以放心用的

@Override

protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int width=setMeasure(widthMeasureSpec);

int height=setMeasure(heightMeasureSpec);

}

private int setMeasure(int size){

int tempSize=1080;

int type=MeasureSpec.getMode(size);

int width=MeasureSpec.getSize(size);

switch (type){

case MeasureSpec.UNSPECIFIED:

width=Math.min(tempSize,width);

break;

case MeasureSpec.AT_MOST:

case MeasureSpec.EXACTLY:

break;

}

return width;

}

2021-02-07

你可能感兴趣的:(Path基础用法)