Canvas和Path的基础用法

Canvas和Path的基础用法

   Canvas和Path是自定义View中非常重要的两个的类,Canvas是画布,就是我们在写自定义View的时候需要绘制的各种东西。我们可以画各种形状,矩形,圆形,椭圆等等。
Path也是个非常强大的类,我们也可以通过Path,画出各种形状,矩形,椭圆等。Path还涉及到一个比较重要的用法,可以画出贝塞尔曲线,我们可以利用这个画出各种动效。
Canvas的基础用法
    当我们在学习自定义View的时候一定会用到Canvas,可能没有深入的了解过,但是在绘制的过程中一定会用到,比方说在学习Paint的时候就是会往自定义View上面绘制很多的东西,这个时候就必须用到Canvas,往Canvas上面画东西。
   Canvas的用法比较重要,重要的是当我们在绘制复杂自定义View的时候往往都需要对画布做一些变换,平移,缩放,旋转等,知道这些技巧,能够让我们再做自定义View的时候 游刃有余。下面就开始看看画布到底有哪些用法。这里只介绍矩阵的变换,先看看矩阵的平移变换。

void translate (float dx, float dy);这个方法用来做矩阵的平移

上代码:

public class CanvasBaseView extends View {
private Paint mPaint;
private RectF mRectF;

public CanvasBaseView(Context context) {
    this(context,null);
}

public CanvasBaseView(Context context, AttributeSet attrs) {
    this(context, attrs,0);
}

public CanvasBaseView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    initPaint();
}

private void initPaint() {
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setStrokeWidth(5);
    mPaint.setStyle(Paint.Style.STROKE);
    mRectF = new RectF(0,0,300,400);
}


@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.save();
    mPaint.setColor(Color.RED);
    canvas.drawRect(mRectF,mPaint);
    canvas.translate(100,100);
    mPaint.setColor(Color.YELLOW);
    canvas.drawRect(mRectF,mPaint);
    canvas.restore();
    mPaint.setColor(Color.GREEN);
    canvas.drawRect(mRectF,mPaint);
}
}

效果图:


canvas_path_1.png
   上面的代码我们一共做了一次画布的平移,画了三个矩形。第一步调用了canvas.save()方法,
   首先我们要知道,在每次调用canvas.drawXXX()方法的时候,其实这个canvas已经是一个新的画布了,只是这个画布所有的位置信息,都跟上一个画布一样形成我们所看的样子。并且在绘制完成的时候,所有的画布都会叠在一起,所以我们感受不到这是一个新的画布,但是我们一旦对画布做了一些变化,这种变化就会立刻明显的体现在我们所绘制的图形上。
所以分析上面的代码,我们首先调用了canvas。save(),方法,这个方法是把我们当前的Canvas的相应的信息保存在画布栈中,这个栈保存是每一张画布的信息,只要调用canvas.save()方法,就是把这张画布进栈了。那么以后当我们继续想应用到这张画布的相关信息的时候就可以把这张画布出栈,这样当我们再次调用canvas.drawXXX()的时候,这时新建的画布就是相应的出栈的画布的信息了,结合代码来看的话就是,我们首先保留了最初的画布信息,然后画了一张红色的矩形。
然后对画布进行了平移的操作后,分别向下,向右平移了100px,此时,在画一张黄色的矩形,最后调用canvas.restore()将相应的画布信息出栈,也就是把画布恢复到最初的状态,此时再画一个绿色的矩形,我们可以看到绿色的矩形覆盖了红色的矩形。

   void scale(float sx, float sy, float px, float py);这个方法是对画布
进行缩放,sx,sy:分别是画布在X,Y方向的缩放值,px,py是画布缩放所围绕的坐标点,默认是坐标的原点。

上代码:

mPaint.setColor(Color.RED);
canvas.drawRect(mRectF,mPaint);
canvas.scale(1.5f,0.5f);
mPaint.setColor(Color.YELLOW);
canvas.drawRect(mRectF,mPaint);

效果图:


canvas_path_2.png
可以看出黄色的矩形,分别在X轴方向放大了1.5倍,在Y轴方向缩小了0.5倍。


void  rotate(float degrees, float px, float py);对画布进行旋转,degree
是画布旋转的角度,旋转的方向是顺时针旋转,px,py是画布旋转时参照的坐标点,简单的说就是画布绕着px,py这个点顺时针旋转degrees角度

上代码:

mPaint.setColor(Color.RED);
canvas.drawRect(mRectF,mPaint);
canvas.rotate(90,300,400);
mPaint.setColor(Color.YELLOW);
canvas.drawRect(mRectF,mPaint);

效果图:


canvas_path_3.png
其实Canvas主要需要知道的就是各种各样的变换。save(),restore()分别表示着进栈和出栈,弄懂这个是最重要的,然后就是一些Canvas的变换的API的使用,会用这些,那么Canvas这个类的基础就差不多了。
Path的基础用法
   Path是一个特别重要的类,我们要画的一些几何图形,基本上Path都可以画,并且Path可以做贝塞尔曲线,有了这些API,实现一些特别的动画效果就有了基础。
    简单介绍一下Path的基础用法,先用Path画个矩形。
    
public class PathBaseView extends View {
private Paint mPaint;
private Path mPath;
private RectF mRect;
public PathBaseView(Context context) {
    this(context,null);
}

public PathBaseView(Context context, AttributeSet attrs) {
    this(context, attrs,0);
}

public PathBaseView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initPaint();
    setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

private void initPaint() {
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(5);
    mPaint.setColor(Color.RED);
    mRect = new RectF(0,0,200,200);
    mPath = new Path();
}


@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPath.reset();
    mPath.addRect(mRect, Path.Direction.CCW);
    canvas.drawPath(mPath,mPaint);
}
}

效果图:


canvas_path_4.png
   解释一下上面的代码,setLayerType(View.LAYER_TYPE_SOFTWARE, null);是
关闭硬件加速的。mPath.addRect(mRect, Path.Direction.CCW);这行代码前面是我们要添加的矩形,后面是我们的路径窄这个矩形上是沿着什么方向添加到Path上的。
Path.Direction.CCW代表是逆时针方向,
Path.Direction.CW代表顺时针方向,方向问题在这里体现的并不是很明显,但是如果我们要绘制一个有方向的文字,沿着这条Path绘制文字,那么这个时候这个方向的作用就会体现出来了。mPath.reset()是设置把这个Path上的所有直线和曲线全部清空,就是重置,
这个很好理解,我们每次onDraw()绘制之前,都应该把上次绘制的Path清空了。



   下面再介绍一下关于画线的几个API,首先是moveTo(float x, float y),就是把
这个Path现在移动到x,y这个点,如果下面要画连接线或者曲线的时候是从这个点开始的lineTo(float x, float y);这个表示在这个Path的最后一个坐标点和x,y这个坐标点之间添加一条线,close();这个表示是否关闭当前的轮廓。

上代码:

mPath.reset();
mPath.moveTo(100,100);
mPath.lineTo(300,300);
mPath.moveTo(100,200);
mPath.lineTo(300,400);
mPath.lineTo(200,100);
canvas.drawPath(mPath,mPaint);

上面这段代码比较有代表性的能够解释上面的API,首先是是重置mPath,然后将这条线移动到(100,100)这个坐标点,然后在(300,300)和mPath上的最后一个点(就是(100,100)这个点)连一条线,然后再将mPath的开始点移动到(100,200)这个点,再画一条连接到(300,400)的线,然后在画一条连接mPath上的最后一个点到(200,100)的一条线,现在在mPath上的最后一个点已经变成了(300,400),所以最终mPath就变成了上面图这样的效果。这里面还没用到close(),下面看看,加上了close()方法后,mPath变成了什么样?

上代码:

mPath.reset();
mPath.moveTo(100,100);
mPath.lineTo(300,300);
mPath.moveTo(100,200);
mPath.lineTo(300,400);
mPath.lineTo(200,100);
mPath.close();
canvas.drawPath(mPath,mPaint);

效果图:


canvas_path_6.png
   根据上面的图,可以看到,mPath最终被连接起来了,并且连接的并不是mPath的第一个起始点,而是最后一个起始点,所以close()方法就是把Path的最后一个点和Path上的最后一个起始点之间连上一条线,构成一个封闭的图形。
简单的介绍一下贝塞尔曲线
    关于贝塞尔曲线的定义,我就不说了,大家自己去百度一下。下面就简单的说一下Path中简单的连接API,这里的API提供二阶贝塞尔和三阶贝塞尔曲线的连接方式。那么下面就看一下这些API
quadTo(float x1, float y1, float x2, float y2);其中x1,x2代表在Path的上一个结束点到x2,y2之间连接的一条线的中间的控制点,控制点的坐标就是(x1,x2);
rQuadTo(float dx1, float dy1, float dx2, float dy2);
其中dx1,dy1,dx2,dy2,代表是相对Path的上一个结束点的相对的坐标,加入上一个结束点的坐标是(dx,dy),那么这个时候控制点的坐标是(dx+dx1,dy+dy1),Path的现在的结束点就是(dx+dx2,dy+dy2);简单来说这个就是想对上一个坐标点的相对坐标。

上代码:

public class PathBaseView extends View {
private Paint mPaint;
private Path mPath;
public PathBaseView(Context context) {
    this(context,null);
}

public PathBaseView(Context context, AttributeSet attrs) {
    this(context, attrs,0);
}

public PathBaseView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initPaint();
    setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

private void initPaint() {

    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(5);
    mPaint.setColor(Color.RED);
    mRect = new RectF(0,0,200,200);
    mPath = new Path();

}
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
     mPath.reset();
    //二阶贝赛尔
    //这里是绝对的坐标
    mPath.moveTo(100,100);
    mPath.quadTo(150,50,200,100);
    mPath.quadTo(250,150,300,100);
    //下面是相对坐标
    //mPath.moveTo(100,100);
   //mPath.rQuadTo(50,-50,100,0);
   //mPath.rQuadTo(50,50,100,0);
  
    canvas.drawPath(mPath,mPaint);
}
}

效果图:


canvas_path_7.png
可以看出,上面是二阶贝塞尔曲线连接成的类似于正弦曲线。用到了绝对坐标和相对坐标。比较简单。下面看一下用三阶贝塞尔曲线。

上代码:

//三阶贝塞尔
mPath.reset();
mPath.moveTo(100,100);
mPath.cubicTo(150,50,250,150,300,100);
//三阶贝塞尔选择相对的函数
//mPath.moveTo(100,100);
//mPath.rCubicTo(50,-50,150,50,200,0);
canvas.drawPath(mPath,mPaint);
canvas_path_8.png
关于Path和Canvas的基础用法就写到这吧,下篇文章会把这两个结合起来加上属性动画写个自定义View。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容