Android自定义控件之基础知识1

前言
Android自定义View是Android初中级开发工程师向高级工程师进阶所必须掌握的一块内容,其重要性不言而喻。接下来的一段时间,我会连续出几篇跟自定义View相关的文章,从易到难,跟大家一起学习Android自定义View。本文讲一个Android很简单的View——DashBoard(仪表盘),以这个例子带大家去学习自定义View的基本绘制,让大家学会自定义View,并最终掌握。

注:本文的Demo在文章的最后
必须要掌握的几个点
在开始我们的绘制DashBoard之前,有几个点是必须要掌握的,这些是绘制的基础,也是前提。
Paint
自定义View的过程就是一个绘制的过程,而绘制就好像我们画画一样,而画画就必须要会画笔,Paint就是我们的画笔。

Paint 类的几个最常用的方法。具体是:

Paint.setStyle(Style style) 设置绘制模式
Paint.setColor(int color) 设置颜色
Paint.setStrokeWidth(float width) 设置线条宽度
Paint.setTextSize(float textSize) 设置文字大小
Paint.setAntiAlias(boolean aa) 设置抗锯齿开关

这里重点讲一下Paint.setStyle(Style style)方法,这个方法设置的是绘制的 Style 。Style 具体来说有三种: FILL, STROKE 和 FILL_AND_STROKE 。FILL 是填充模式,STROKE 是画线模式(即勾边模式),FILL_AND_STROKE 是两种模式一并使用:既画线又填充。它的默认值是 FILL,填充模式。只有当Style是STROKE 和 FILL_AND_STROKE时,Paint.setStrokeWidth(float width)才有意义,你全是填充的就不涉及什么线条宽度了。
canvas
Paint是画笔,可画画光有画笔还不行,还必须得有画布,Canvas就是画布。Canvas这个类是绘制最重要的类,没有之一,几乎所有绘制的方法都出自于这个类。
坐标系
方法先不讲,先讲一下坐标系,在Android 里,每个View 都有一个自己的坐标系,彼此之间是不影响的。这个坐标系的原点是 View 左上角的那个点;水平方向是 x 轴,右正左负;竖直方向是 y 轴,下正上负(注意,是下正上负,不是上正下负,和上学时候学的坐标系方向不一样也就是下面这个样子。!
在这里插入图片描述
这个坐标非常重要,因为我们所有的绘制都是在这个坐标系的基础上开展的,而关于坐标系还有这么个两个方法要特别注意:

Canvas.rotate(float degrees)//旋转坐标系,正角度顺时针,负角度逆时针

Canvas.translate(float dx, float dy)

注意:以上两个方法的操作的对象是坐标系,跟View本身没有关系,之所以使用是为了让我们更好、更方便地绘制View。

方法
Canvas最重要也最常用的方法都是drawXXX()方法,方法太多了,我不可能一一列举,写几个最常用的,余下的请自行google

drawCircle(float centerX, float centerY, float radius, Paint paint) 画圆

基本看参数名字就能猜出来是啥意思了,前面讲了坐标系的概念,前两个参数就是圆心的X、Y坐标了,第三个是半径大小,最后一个是画笔。

drawRect(float left, float top, float right, float bottom, Paint paint) 画矩形 (参数啥意思基本都能猜出来,不多讲,不行还是google)
drawOval(float left, float top, float right, float bottom, Paint paint) 画椭圆
drawLine(float startX, float startY, float stopX, float stopY, Paint paint) 画线

drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 绘制弧形或扇形

left, top, right, bottom 描述的是这个弧形所在的椭圆;startAngle 是弧形的起始角度(x 轴的正向,即正右的方向,是 0 度的位置;顺时针为正角度,逆时针为负角度),sweepAngle 是弧形划过的角度;useCenter 表示是否连接到圆心,如果不连接到圆心,就是弧形,如果连接到圆心,就是扇形。

drawPath(Path path, Paint paint) 画自定义图形

这里Path对象要讲一下,Path.addXxx()——添加子图形,例如Path.addCircle(float x, float y, float radius, Direction dir) 添加圆;Path.xxxTo() ——画线(直线或曲线)lineTo(float x, float y) / rLineTo(float x, float y) 画直线.

小结
以上就是我们开始绘制DashBoard之前还需要掌握的基础,因为都用得到。Paint是画笔,主要就是设置画笔相关的属性,颜色、大小、风格等等;canvas是画布,坐标系的概念必须清楚,重要的几个方法也必须知道。
DashBoard
先上个图
在这里插入图片描述
看图其实很简单,基本上就分为三步,第一份画弧;第二步画刻度;第三步画指针。

画弧线
画弧线之前,我简单讲一下自定义View的流程,创建一个DashBoard的类型继承View,重写构造方法和onDraw(Canvas canvas)
public class DashBoard extends View {
public DashBoard(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initPaint();//初始化Paint
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}

初始化Paint
private void initPaint(){
mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
mPaint.setStyle(Paint.Style.STROKE);//画线模式
mPaint.setStrokeWidth(Utils.px2dp(2));//线宽度
mPaint.setColor(Color.BLACK);
}

做好以上初始化工作,我们就开始第一步画弧线。调用Canvas.drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)这个方法上面有介绍过,这里就不详细讲了,前四个参数很好设置,我们定义圆心在View的中心位置,即(getWidth()/2,geHeight()/2),半径150dp,那么前四个参数就有了,第六个参数sweepAngle是划过的度数,这个我们定义为240度,第七个是否连接中心,false,我们不需要连接中心,最后一个放自己的Paint就行了,现在的关键就是第五个参数,开始角度的计算,下面我出张图帮助大家计算一下
在这里插入图片描述
图中画得应该比较清楚了,不过多解释,直接上代码

private void drawArc(Canvas canvas){
rectF = new RectF(getWidth() / 2 - RADIUS, getHeight() / 2 - RADIUS,
getWidth() / 2 + RADIUS, getHeight() / 2 + RADIUS);
canvas.drawArc(rectF,90+(360-SWEEPANGLE)/2,SWEEPANGLE,false,mPaint);
}

效果图
在这里插入图片描述
画刻度
关于画刻度,其实就是画线吗,那画线的方法拿过来看一下,Canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint) ,需要线的起始点和结束点的坐标,如果我要画21个刻度,那需要21个点的刻度都算一遍,我去,谁可能这么干啊。放心,我们当然不会这么干了,下面提供两种方式。
第一种方式
思路:坐标系的旋转+平移

之前在讲Canvas里提过,每一个Android的View都对应着有一个坐标系,坐标原点在View的左上角(可以看一下上面的那张图),现在如果我们把坐标原点平移到圆心的位置,并且再顺时针旋转30°,那么当前的坐标系就是下面这样的
在这里插入图片描述
那么在我们平移旋转以后,在画右下角第一个刻度的时候,就相当于这个刻度的起始点和结束点的纵坐标都是0,因为我们把原点移动到了圆心,而结束点的横坐标就是圆的半径(RADIUS),而起始点的横坐标就是(半径-刻度线的长度)。下面我还是用一张图来解释一下
在这里插入图片描述
这样第一个刻度线,我们就画出来了,现在假定我们要画21个刻度线,21刻度线对应20个间隔,总角度是240度,每个刻度线间隔角度就是240/20即12度,所以其他的刻度线就可以让坐标系每次逆时针旋转12度画一次,用代码表达一下会更清晰。
private void drawDegree(Canvas canvas){
canvas.translate(getWidth()/2,getHeight()/2);
canvas.rotate(30);
for (int i=0;i<20;i++){
//Utils.px2dp(10)是刻度线的长度,为10dp
canvas.drawLine(RADIUS-Utils.px2dp(10),0,RADIUS,0,mPaint);
canvas.rotate(-SWEEPANGLE/20);//逆时针选择 负值是逆时针
}
//最后一根线
canvas.drawLine(RADIUS-Utils.px2dp(10),0,RADIUS,0,mPaint);
canvas.rotate(240-30);//旋转回去的角度
canvas.translate(-getWidth()/2,-getHeight()/2);

}

画完以后的效果图
在这里插入图片描述
这个图我故意没有缩放,细心的你可能已经发现了第一个和最后一个刻度明显有点不自然,我再放大一下
在这里插入图片描述
看图我们可以知道,我们在画刻度的时候,Paint就是我们的画笔,默认是宽度的,我们画刻度的纵坐标是0,但是实际画的时候是把画笔的中间位置放在0的坐标上,这就导致了好像空了一半Paint出来,知道了什么原因其实解决起来也很简单,就是把起始点和结束点的纵坐标相应的提高半个画笔的高度,直接看代码吧
private void drawDegree(Canvas canvas){
canvas.translate(getWidth()/2,getHeight()/2);
canvas.rotate(30);
for (int i=0;i<20;i++){
//纵坐标下正上负,向上提高,加负值,即-mPaint.getStrokeWidth()/2
canvas.drawLine(RADIUS-Utils.px2dp(10),-mPaint.getStrokeWidth()/2,RADIUS,-mPaint.getStrokeWidth()/2,mPaint);
canvas.rotate(-SWEEPANGLE/20);
}
//最后一个点,因坐标系已经旋转了240度,向上提高,加整值,即mPaint.getStrokeWidth()/2
canvas.drawLine(RADIUS-Utils.px2dp(10),mPaint.getStrokeWidth()/2,RADIUS,mPaint.getStrokeWidth()/2,mPaint);
canvas.rotate(240-30);//旋转回去的角度
canvas.translate(-getWidth()/2,-getHeight()/2);
}

看了注释,你会发现第一个点和最后一个点的提高方式不同,这也是为什么上面”相应的“三个字我要加粗了,再看一眼修改后的效果图
在这里插入图片描述
第二种方式思路:使用PathMeasure测量弧线长度,利用PathDashPathEffect来画刻度
简单讲一下PathMeasure和PathDashPathEffectPathMeasure:用来测量路径的长度,public PathMeasure(Path path, boolean forceClosed),通过PathMeasure.getLength()PathDashPathEffect:Paint.setPathEffect(PathEffect effect)给图形的轮廓设置效果的,PathDashPathEffect是PathEffect的一个子类,它的构造方法 PathDashPathEffect(Path shape, float advance, float phase, PathDashPathEffect.Style style) 中, shape 参数是用来绘制的 Path ; advance 是两个相邻的 shape 段之间的间隔,不过注意,这个间隔是两个 shape 段的起点的间隔,而不是前一个的终点和后一个的起点的距离; phase 和 DashPathEffect 中一样,是虚线的偏移;最后一个参数 style,是用来指定拐弯改变的时候 shape 的转换方式。style 的类型为 PathDashPathEffect.Style ,是一个 enum ,具体有三个值:TRANSLATE:位移,ROTATE:旋转,MORPH:变体
知道了这两个方法,我们就可以先用PathMeasure拿到弧线的长度,除以20获得每个间隔的长度,然后通过Paint.setPathEffect(new PathDashPathEffect())方法来画刻度就行了,直接上代码private void drawDegree2(Canvas canvas){
//刻度的路径
dash=new Path();
//Path.Direction.CW顺时针方向 同时顺时针切线方向为X轴正向
dash.addRect(0,0,Utils.px2dp(2),Utils.px2dp(10), Path.Direction.CW);
//弧线长度的路径
Path length=new Path();
length.addArc(rectF,90+(360-SWEEPANGLE)/2,SWEEPANGLE);
//测量弧线长度
pathMeasure=new PathMeasure(length,false);
//这里(pathMeasure.getLength()-mPaint.getStrokeWidth())/20 弧线长度之所以减去Paint的宽度跟我第一种方式去掉宽度是一个意思
mPaint.setPathEffect(new PathDashPathEffect(dash,
(pathMeasure.getLength()-mPaint.getStrokeWidth())/20,0, PathDashPathEffect.Style.ROTATE));
canvas.drawArc(rectF,90+(360-SWEEPANGLE)/2,SWEEPANGLE,false,mPaint);
mPaint.setPathEffect(null);
}
这里我就不细讲了,注释还是比较清楚,效果图跟第一种方式是一样的就不贴图,个人还是更加推荐第一种的画刻度方式。画指针画指针呢,就比较简单了,其实就是调用画线的方法,先把坐标系平移动原点位置,设置一个当前的角度currentAngle还有指针长度INDICATOR,唯一有一点难度的就是计算结束点的横纵坐标,需要用到三角函数的知识横坐标:Math.cos(Math.toRadians(currentAngle))*INDICATOR纵坐标:Math.sin(Math.toRadians(currentAngle))*INDICATOR
很简单,上代码private void drawIndicator(Canvas canvas){
canvas.translate(getWidth()/2,getHeight()/2);
canvas.drawLine(0,0,
(float) Math.cos(Math.toRadians(currentAngle))*INDICATOR,
(float)Math.sin(Math.toRadians(currentAngle))*INDICATOR,
mPaint);
canvas.translate(getWidth()/2,getHeight()/2);
}
最后Android自定义View是Android比较难的一块内容,本文主要通过绘制DashBoard来讲基本的绘制,Paint和Canvas的基本用法,接下来的一段时间内,我会继续出自定义View相关的内容

本文来源于网络:
作者:肖邦kaka
链接:https://www.jianshu.com/p/a7b2f60067f8
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值