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.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);
效果图:
可以看出黄色的矩形,分别在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主要需要知道的就是各种各样的变换。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);
}
}
效果图:
解释一下上面的代码,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);
效果图:
根据上面的图,可以看到,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);
}
}
效果图:
可以看出,上面是二阶贝塞尔曲线连接成的类似于正弦曲线。用到了绝对坐标和相对坐标。比较简单。下面看一下用三阶贝塞尔曲线。
上代码:
//三阶贝塞尔
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);
关于Path和Canvas的基础用法就写到这吧,下篇文章会把这两个结合起来加上属性动画写个自定义View。