原文:https://hencoder.com/ui-1-1/
public class UiView_1_1 extends View{
public UiView_1_1(Context context) {
super(context);
}
public UiView_1_1(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public UiView_1_1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
通过上述代码,就实现了一个自定义View的基础模版,但是为了实现自定义View,我们还需要相关的绘制,在这里我们要重写onDraw()
方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
在这里传入了Canvas
,可以理解成画布
的意思,自定义View简单理解就是在屏幕的坐标系中去绘制图案的过程,在这里就设计到坐标系的知识,Android规定以屏幕的左上定点为原点
,向右
为x轴的正向,向下
为y轴的正向
有了画布以后,我们还需要一支画笔,Android提供了Paint
类可供我们使用,所以我们需要在创建一支画笔就可以了
Paint paint = new Paint();
//设置画笔风格:填充,勾边,即填充又勾边
paint.setStyle(Paint.Style.STROKE);
//设置画笔的颜色
paint.setColor(Color.BLACK);
//设置画笔的宽度
paint.setStrokeWidth(5);
//设置抗锯齿开关
paint.setAntiAlias(true);
既然有了坐标系,那么说我们就可以通过坐标系绘制图形了
下面是绘制基础图形:
//画圆:注意这里的cx 和 cy 是圆心的坐标
canvas.drawCircle(250,250,200,paint);
//画方形:注意left是左边到y轴的距离,top是上边到x轴的距离,right是右边到y轴的距离,bottom是底边到x轴的距离
canvas.drawRect(100, 100, 200, 200, paint);
//画椭圆:api最小为16,当椭圆的上下边距和左右边距相等时,就为圆了
canvas.drawOval(200,50,400,120,paint);
//画线
canvas.drawLine(20,20,80,80,paint);
//设置端头的形状
paint.setStrokeCap(Paint.Cap.ROUND);
//绘制单个点
canvas.drawPoint(50,50,paint);
//绘制多个点
float[] pts = {60,60,70,70,80,80};
canvas.drawPoints(pts,paint);
//绘制多条线,注意要为复数
canvas.drawLines(pts,paint);
//绘制圆角矩形,rx和ry代表圆角的横向半径和纵向半径
canvas.drawRoundRect(300,300,500,500,50,50,paint);
paint.setStyle(Paint.Style.FILL); // 填充模式
//drawArc()是使用一个椭圆来描述弧形的。left, top, right, bottom 描述的是这个弧形所在的椭圆;
//绘制弧形或扇形,startAngle:开始的角度,sweepAngle:滑过的角度,useCenter:是否连接圆心,
canvas.drawArc(400,200,900,500,-110,100,true,paint);
canvas.drawArc(400,200,900,500,20,140,false,paint);
//注意滑过的角度,顺时针为正角度,逆时针为负角度
paint.setStyle(Paint.Style.STROKE);
canvas.drawArc(400,200,900,500,-180,60,false,paint);
//将Resource中的图片文件转换为bitmap
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
canvas.drawBitmap(bitmap,200,200,paint);
//设置字体的大小
paint.setTextSize(18);
//绘制文字
canvas.drawText("Hello World",400,400,paint);
在知识二中,我们介绍了基础图形的绘制,比如圆形,方形,椭圆形,扇形等图案,但是有一些组合图形是无法绘制出来的
如果我们要绘制比如❤️形,通过上面方式就无法满足了,所以,我们可以使用Android提供的Path
类,使用方式:
Path path = new Path();
Paint paint = new Paint();
//path的相关绘制,在这里我们在画布中传入path和paint即可
canvas.drawPath(path,paint);
paint.setStyle(Paint.Style.FILL);
//使用Path对象绘制圆,其实和drawCircle一样,但是这个一般可以用来组合
//顺时针 (CW clockwise) 和逆时针 (CCW counter-clockwise)
path.addCircle(300,600,200,Path.Direction.CW);
canvas.drawPath(path,paint);
//绘制线:lineTo表示从(0,0)-> (100,0) rLineTo表示相对于(100,0)->(0,200)
paint.setStyle(Paint.Style.STROKE);
path.lineTo(100,0);
path.rLineTo(0,200);
canvas.drawPath(path,paint);
//绘制弧形:forceMoveTo 参数的意思是,绘制是要「抬一下笔移动过去」,还是「直接拖着笔过去」,区别在于是否留下移动的痕迹
paint.setStyle(Paint.Style.STROKE);
path.lineTo(100, 100);
//path.addArc() 只是一个直接使用了 forceMoveTo = true 的简化版 arcTo()
path.arcTo(100, 100, 300, 300, -90, 90, true);
canvas.drawPath(path,paint);
//绘制封闭的图形
//不是所有的子图形都需要使用 close() 来封闭
// 当需要填充图形时(即 Paint.Style 为 FILL 或 FILL_AND_STROKE),Path 会自动封闭子图形
path.moveTo(100, 100);
path.lineTo(200, 100);
path.lineTo(150, 150);
path.close(); // 使用 close() 封闭子图形。等价于 path.lineTo(100, 100)
canvas.drawPath(path,paint);
关于这一类主要是围绕着:
setFillType(FillType fillType)
去研究
//方法中填入不同的 FillType 值,就会有不同的填充效果
Path.setFillType(Path.FillType ft) 设置填充方式
//=> FillType 的取值有四个
//-> EVEN_ODD
//-> WINDING (默认值)
// 其中后面的两个带有 INVERSE_ 前缀的,只是前两个的反色版本
//-> INVERSE_EVEN_ODD
//-> INVERSE_WINDING
EG:
Path path1 = new Path();
paint.setStyle(Paint.Style.FILL);
// 顺时针 (CW clockwise) 和逆时针 (CCW counter-clockwise)
path1.addCircle(400,800,200, Path.Direction.CW); //顺时针
path1.addCircle(600,800,200, Path.Direction.CCW); //逆时针
path1.setFillType(Path.FillType.WINDING);
canvas.drawPath(path1,paint);
对于平面中的任意一点,向任意方向射出一条射线,这条射线和图形相交的次数(相交才算,相切不算哦)
如果是奇数
,则这个点被认为在图形内部
=>要被涂色的区域
如果是偶数
,则这个点被认为在图形外部
=>不被涂色的区域
射线每
穿过图形中的一条线,内外状态就发生一次切换
,EVEN_ODD =>「交叉填充」的模式
它需要你图形中的所有线条都是有绘制方向的
同样是从平面中的点向任意方向射出一条射线,但计算规则不一样:
- 以0 为初始值
,对于射线
和图形
的所有交点
- 遇到每个顺时针
的交点把结果加 1
- 遇到每个逆时针
的交点把结果减 1
- 最终把所有的交点
都算上
- 结果如果不是 0
,则认为这个点在图形内部
=> 是要被涂色的区域
- 如果是 0
,则认为这个点在图形外部
=> 是不被涂色的区域
所以,完整版的 EVEN_ODD 和 WINDING 的效果应该是这样的: