Android自定义绘制1-1 Plus
上一个文章是看着扔物线的视频写的。写玩之后,发现他的文章还有很多内容。尴尬。
接着写呗。
自定义绘制知识的4个级别
1.Canvas的drawXXX()系列方法以及Paint类的一些常见方法。
canvas的drawXXX()是自定义绘制的最基本操作。掌握了这些方法,才知道怎么绘制内容。例如,怎么画圆,怎么画方块,怎么画文字。这些内容再配合Paint的一些常见方法对绘制内容的颜色和风格进行简单配置,就能应付大部分绘制需求了。
2.Paint的完全攻略
Paint可以做的事,不仅仅是设置颜色,阴影,粗细。它可以做的风格真的非常多,非常细。比如双线性过滤,拐角形状,特效。
3.Canvas对绘制的辅助--范围裁切和几何变化。
大多数的时候,他们并不会被用到,但当被用到的时候,都是很酷的功能。
以后再也不要怕设计师设计多炫酷的功能了。
4.使用不同的绘制方法来控制绘制顺序。
有人说,绘制顺序,我多整几个view给他弄出来,但这样通常都有性能问题。所以我们用绘制顺序解决的也就是性能问题。因为用绘制顺序,一个view就能搞定。
第一个系列的内容,canvas.drawXXX以及paint最基本的使用。
1.一切的开始onDraw()
drawXXX()系列的方法和Paint的基础掌握了,就能够应付简单的绘制需求。它们主要包括:
1.Canvas类下的所有draw打头的方法。例如drawCircle(),drawBitmap()
2.Pain类的几个常用方法,比如:
paint.style 绘制模式
paint.color 设置绘制颜色
paint.strokeWidth 设置线条宽度
paint.isAntiAlias 抗锯齿开关
Canvas.drawColor 颜色填充
这是最基本drawXXX方法,是在整个绘制区,统一涂上指定的颜色。
canvas.drawColor(resources.getColor(R.color.black))
这个就直接把绘制区变黑。
也可以通过透明度设置遮罩。
类似的还有drawRGB和drawARGB,效果都是一样的。
这类颜色填充方法一般用于在绘制之前设置底色,或者在绘制之后设置半透明蒙版。
drawCircle(float cx, float cy, float radius, @NonNull Paint paint)
前面两个参数是圆心的坐标。第三个是圆的半径。他们共同构成了这个圆的基本信息。第四个参数是Paint.它提供除了基本信息以外的所有风格信息。
在Android中,每个View都有一个自己的坐标系。彼此之间是不影响的。坐标系的原点就是View左上角的那个点。所以当我们设置绘制这个圆的时候,比如我们设置圆中心的坐标是300,300,那么它就是这样的:
如果你想画的圆不是实心的,而是空心的,可以这样:
paint.style = Paint.Style.STROKE
这里style有三种模式,fill,stroke,fill_and_stroke。分别是填充,描边,填充和描边。
你在stroke和fill_and_stroke模式下,你还可以设置线条的宽度:
paint.strokeWidth = 20f
在绘制的时候,为了让图形和文字的边缘更圆滑,往往需要开启抗锯齿:
paint.isAntiAlias = true
那么抗锯齿这么好,为什么不默认开启呢?
实质上,抗锯齿的发生是因为图形分辨率过低,导致人眼察觉出了画面中的像素颗粒而已。
那么,为什么开启抗锯齿,就看起来圆滑了呢?因为抗锯齿的原理是,修改图形边缘的颜色。而让图形看起来更加圆滑。
可以看到,未开启抗锯齿的时候,所有像素的颜色同样都是黑色。而开启后,边缘的颜色被略微改变了。所以,抗锯齿,会造成图形的失真。
除了drawCircle,还可以画别的图形。
另外,他还有另外两个重载方法,drawRect(RectF rect, Paint paint)
和 drawRect(Rect rect, Paint paint)
,可以让你直接填写rectF或者rect来绘制矩形。
canvas.drawPoint(50f,50f,paint)
再来看这个drawPoint,就是画点,x和y是点的坐标。点的大小可以通过strokeWidth来设置。
点的形状可以通过strokeCap来设置。
strokeCap除了ROUND之外还可以用BUTT,和SQUARE
有点像FILL模式下的drawCircle和drawRect。效果是一样的,看自己喜欢,用哪个。
那么为什么要搞两个API呢?因为drawPoint可以画多个点。
这里使用drawPoints,可以传递一个数组,这个数组里每两个为一对坐标。
offset参数,是指在数组里跳过几个数字,后面的count就是绘制几个点数。
paint.style = Paint.Style.FILL
paint.strokeWidth = 20f
canvas.drawOval(50f,50f,300f,200f,paint)
paint.style = Paint.Style.STROKE下:
另外,他还有一个重载方法,drawOval(rect:RectF,paint) 也可以使用RectF来绘制。
可以使用drawLine来绘制线:
canvas.drawLine(50f,50f,200f,500f,paint)
因为这个直接不是封闭图形,所以style对他没有影响。
也可以通过drawLines绘制多个点。也就是drawLine的复制版。
val floatArray = floatArrayOf(20f,20f,50f,50f,100f,100f,120f,300f,200f,400f,200f,100f) canvas.drawLines(floatArray,paint)
接着,再来看drawRoundRect,也就是带圆角的矩形。
canvas.drawRoundRect(100f,100f,200f,200f,10f,10f,paint)
接着,再来看drawArc。这个是绘制弧形或者扇形的。
drawArc是使用一个椭圆来描述弧形的。
paint.style = Paint.Style.STROKE
canvas.drawArc(100f,100f,200f,200f,-100f,100f,true,paint)
首先,前面4个参数是椭圆的4个边界点的坐标,startAngle是弧形的起始角度。X轴的正向是0度的位置,逆时针为负角度,顺时针为正角度。sweepAngle是弧形划过的角度,注意这里的划过,是指在起始角度顺时针划。userCenter表示起始弧度和划过之后的终点位置是否连接中心点。如果连接了,那就是弧形。如果不连接,那就是扇形。
canvas.drawArc(100f,100f,200f,200f,-100f,100f,false,paint)
我们再来看下fill模式下。
paint.style = Paint.Style.FILL
canvas.drawArc(100f,100f,200f,200f,-100f,100f,true,paint)
paint.style = Paint.Style.FILL canvas.drawArc(100f,100f,200f,200f,-100f,100f,false,paint)
以上就是cavans所有的简单图形绘制,除了简单图形绘制,还可以使用path绘制复杂的自定义图形。
drawPath(Path path,Paint paint)
这个方法有点复杂的。
前面说到的方法都是绘制给定的图形。而drawPath可以绘制自定义的图形。像涂鸦板都是用这个方法去做。
这里的path就是描述路径对象。
比如绘制心形:
Path可以描述直线,二次曲线,三次曲线,圆,椭圆,矩形,弧形,圆角矩形,把这些图形结合起来,就可以绘制出很多复杂的图形。
Path有两类方法,一类是直接描述路径,另一类就是辅助的设置或者计算。
Path的第一类方法:直接描述路径
这一类的方法还可以再细分为2组:添加子图和画线(直线或者曲线)
第一组:addXXx---添加子图形
addCircle(float x, float y, float radius, Direction dir)
添加圆
前三个参数是圆的基本信息,最后一个参数是圆的路径的方向。
不过对于普通情况,填逆时针或者顺时针。没有影响。我们一般需要选择填充图形或者描边。
并且在图形出现相交的时候,我们需要判断填充范围。比如:
在使用Path.addCircle新增一个圆之后,就可以使用canvas.drawPath来画出来。
path.addCircle(300f,300f,200f,Path.Direction.CW)
canvas.drawPath(path,paint)
除了addCircle,还有别的addOval,添加椭圆,addRect添加矩形,addRoundRect添加圆角矩形,addPath(path)添加另一个path.
上面这几个方法的用法都和addCircle差不多。
第二组方法:xxxTo()-----画线(直线或者曲线)
和第一组方法的区别是第一组是添加完整的封闭图形(addPath除外),这一组只是添加一条线。
lineTo(float x,float y)/ rLineTo(floatx, float y)画直线
从当前位置向目标位置画一条直线。x y 是目标位置的坐标。这两个方法的区别是lineTo的参数是绝对目标,而rLineTo的的参数是相对当前位置的相对坐标。前缀r的意思就是relative,相对的。
当前位置:所谓当前位置指的就是最后一次调用画Path的方法的终点位置。初始值为原点(0,0)
paint.style = Paint.Style.STROKE
path.lineTo(100f,100f) //由当前位置(0,0)向(100,100)画一条直线
path.rLineTo(100f,0f) //由当前位置(0,0)向正右方100像素的位置画一条直线
canvas.drawPath(path,paint)
quadTo(float x1, float y1, float x2, float y2) / rQuadTo(float dx1, float dy1, float dx2, float dy2)画二次贝塞尔曲线
这条贝塞尔曲线的起点就是当前位置,而参数中的x1,y1,x2,y2分别是控制点和终点的坐标。
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) / rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 画三次贝塞尔曲线
和上面二次贝塞尔曲线同理
moveTo(float x, float y)/ rMoveTo(float x, float y)移动到目标位置
不论是直线还是而塞尔曲线,都是以当前位置作为起点,而不能指定起点。但是咱们可以通过moveTo(x, y)或者rMoveTo(x,y)来改变当前位置,从而达到间接的设置这些方法的起点。
paint.style = Paint.Style.STROKE
path.lineTo(100f,100f) //由当前位置(0,0)向(100,100)画一条直线
path.moveTo(200f,100f)//移动
path.lineTo(200f,0f)//画竖线
canvas.drawPath(path,paint)
moveTo虽然不能添加图形,但是他可以设置起点。所以这是一个非常重要的辅助方法。
arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) / arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo) / arcTo(RectF oval, float startAngle, float sweepAngle)画弧形
这个方法和canvas.drawArc比起来。少了一个参数useCenter,但是多了一个参数,forceMoveTo.
为啥会这样呢?因为arcTo只用来画弧形,而不能用来画扇形。所以这个参数就没用了。
那么这个forceMoveTo又是啥意思
绘制是抬一下笔过去,还是拖着笔迹过去。区别在于是否留下移动的痕迹。
paint.style = Paint.Style.STROKE
path.lineTo(100f,100f) //由当前位置(0,0)向(100,100)画一条直线
path.arcTo(100f,100f,300f,300f,-90f,90f,true) //强制移动到弧形起点(无痕迹)
canvas.drawPath(path,paint)
path.arcTo(100f,100f,300f,300f,-90f,90f,false) //留下痕迹
addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle) / addArc(RectF oval, float startAngle, float sweepAngle)
这又是一个弧形方法。一个addArc,一个arcTo,区别在哪?
其实非常简单,这里少了一个参数 foreceMoveTo,默认为true了。
close()封闭当前子图形
什么是封闭呢,就是由当前位置向当前子图形的起点,画一条线。
先看一下没有封闭的:
paint.style = Paint.Style.STROKE
path.moveTo(100f,100f)
path.lineTo(200f,100f) //由当前位置(0,0)向(100,100)画一条直线
path.lineTo(150f,150f)
如果这个时候我们封闭呢?
paint.style = Paint.Style.STROKE
path.moveTo(100f,100f)
path.lineTo(200f,100f) //由当前位置(0,0)向(100,100)画一条直线
path.lineTo(150f,150f)
path.close()
大家可能会想到我这个时候,使用lineTo起点也是可以办到的,是的,这个时候和Lineto是完全等价的。
所谓子图形,就是一次不间断的连线。
另外,当style为fill,或者为fill_and_stroke的时候,会自动连线。
Path方法第二类:辅助的设置或计算。
这类方法的使用场景比较少。不必学太多。只学期中一个比较常用的:
setFillType(FillType fillType)
前面有说过,path.setFillType是用来设置图形自相交时的填充算法的。
FillType的取值一共有4个:
1.EVEN_ODD
2.WINDING 默认值
3.INVERSE_EVEN_ODD
4.INVERSE_WINDING
两个前面带有INVERSE前缀的, 是1和2的反色版本。
WINDING是全填充,EVEN_ODD是交叉填充。
这个是简单粗暴版本,也就是通常情况下的。
drawBitmap(Bitmap bitmap, float left, float top, Paint paint)画Bitmap
绘制Bitmap对象,期中left,top是要把bitmap要绘制到的位置坐标。
drawText(String text, float x, float y, Paint paint) 绘制文字
界面里所有显示的内容都是绘制出来的,包括文字。drawText这个方法就是绘制文字的。
参数text就是用来绘制的字符串,x,y是绘制的起点坐标。