开始正式的文章之前,先说几句闲话吧:这是一个初学自定义 View 的简单科教类文章,并没有特别高深的内容,所以你完全不必担心,毕竟我也是从不会到会没多久,在这里我会忽略掉一些细节点,并不是说这些不重要,而是它会干扰你的学习重点,所以这些细节需要你自己实际体会才能有更深的理解,在文章中就不过多的说了,后面可以尝试着更改代码中的一些样式参数自行体会就好。

    下面我们正式开始:

    首先,我们明确一个目的,就是我们要做一个表,这个表是圆的,有 3 个指针,有刻度,有数字...看上去是不是很简单?

    好,我们下面就开始以这个目的为基础的编程!

    首先,我们需要定义一个 View,就是普通的 Java 类而已,无非就是继承了一下比较“懒” 的父类----View 而已。不过正是因为这个“懒”,才给了我们 Android 中各种丰富的界面,以及让我们程序员比ge拼zhong技zhuang能bi的舞台。Ok,相信你已经创建好了,那么写好他的 3 个构造方法就行,剩下我们重点介绍。这里提一句构造方法,只有 Context 参数的构造方法是让我们在代码中直接实例化这个类用的,而剩下的 2 个是在 xml 中定义 View 的时候系统调用的。具体细节不用关系,我们赶紧看重要的 OnDraw 方法。

 duang....下面就是我们的核心代码出现的地方:    

protected void onDraw(Canvas canvas)

    这个方法不是给我们调用的,所以你关心的就是这里面究竟干了什么就可以了。究竟要干嘛呢?当然是画一个表,这个表是圆的,有 3 个指针,有刻度,有数字...东西比较多,我们一个一个来!

首先,我们在画画的时候会定义一个类叫做 Paint 类,类如其名,就是笔!所有画画实现的效果,都是出自它的设置。我们先定义一个画最外面大圆圈的笔

Paint paintCircle = new Paint();

  好,有了笔,我们画在哪里呢?当然是纸上!其实在 Android 中不叫纸,叫做画布,就是在我们这个方法里面传入的那个参数--Canvas !Canvas是最后你要画画的地方,所以它来规定画什么,这里我们当然要画圆圈了啊!于是下面的代码就出现了..

canvas.drawCircle(mWidth / 2,
mHeight / 2, mWidth / 2 - width_circle, paintCircle);

  好的,你看见了不少数字,我们解释一下:前面 2 个代表了坐标的 x , y 轴上的坐标,我们直接获取手机屏幕的中心点就好了,第二个参数是我们圆圈的半径,我们以宽的一半为半径就行,最后一个参数呢,就是我们刚才定义的画笔。好,这时一个圆圈就算搞定了。

  有了最外面的圆圈,我们还要有刻度:首先,整点和非整点的刻度要区别,那么就体现在它们的长度和宽度上;其次,每个刻度说白了就是线段,我们需要告诉计算机这个线段的起点终点是什么。

   第一个好理解,我们无非就是计算就好了嘛:1,2,3...11,12 一共 12 个整点。一共 60 个刻度,除过这 12 个剩下的都是小的,因为一圈是 60 个刻度,一圈是 360 度,所以每一个刻度之间的距离是 360/60 度。至此,我们有数据了,可是不能每次都算每个刻度的 2 个点坐标吧...这个烦啊...而且有一个点还必须在圆圈上,各种三角函数...我们先放松一下,如果在实际生活中,你肯定不会用笔这么画的,因为这太 2 了!观察一下我们发现,画 12 点和 6 点是最舒服的,其中 12 点比 6 点舒服!要是都画 12 点的就好了...擦擦你的口水吧,我们让你梦想成真!关键的点就在于不要我们懂,让画布动不就完了!我们每次要做的就是,同一个点,向下画,画布移动角度,再重复!好比流水线上的工人一样,简单快捷!类似酱...

canvas.rotate(360 / 60, mWidth / 2, mHeight / 2);

这个就是画布旋转的代码了,第一个参数是旋转的角度,旋转中心的坐标,这里当然是我们画布的中心啦!

canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2 + width_circle, mWidth / 2, mHeight / 2 - mWidth / 2 + lineLength, painDegree);

上面这行代码呢,是画线段的,前四个参数分别是起点坐标,重点坐标,最后一个参数呢,当然是我们的画笔 Paint 啦!我们只需要用 for 循环执行就好,当然要进行是否是整点的判断来区分线段的宽窄。

    下面是我们的文字,我们的文字是在线段下面的,可是有一个问题,这个文字不能用我们旋转画布那一招进行,因为它会歪....所以我们要进行必要的数学计算...这个地方简单说一下就好。绘制文字,我们用的 api 是这个:

public void drawText(String text, float x, float y, Paint paint)

第一个是只我们要绘制的文字,x 和 y 当然还是我们的绘制的坐标,最后一个参数依旧是画笔 Paint。

  这里稍微说一点:我们表中的文字长度有 2 种,但是用同一种处理坐标的方式肯定是行不通的,要做好偏移计算的准备。因为毕竟 12 点和 1 点是有区别的。

  最后我们要画的是 3 个指针:简单的说,就是 3 个线段,只不过要让它们动起来罢了。其中秒针最人性,我管你分针时针动不动,我到了时间就动!分针要看秒针脸色,秒针转一圈,我才能动一下...时针最惨,大家懂得..所以我们就出现了下面的代码,这里我们以分针为例:

//绘制分针
private void drawMinute(Canvas canvas, Paint paint) {
float degree = (float) (minute * 360 / 60);
canvas.rotate(degree, mWidth / 2, mHeight / 2);
canvas.drawLine(mWidth / 2, mHeight / 2, mWidth / 2, mHeight / 2 - (mWidth / 2 - width_circle) * density_minute, paint);
canvas.rotate(-degree, mWidth / 2, mHeight / 2);
}

这里的 minute 是一个全局变量,代表了分钟的数字,每次绘制的偏移都是因为它而改变的。当然了,这个方法只是一个单调的绘画,多次调用就会出现动画效果了。

    剩下的就是我们如果开始动画的问题,代码如下:

@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();

refreshThread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
SystemClock.sleep((long) refresh_time);
postInvalidate();
}
}
});
refreshThread.start();
}

    这个方法呢,是当我们自己的 View 被系统添加到 Activity 体系中的时候回调的方法,在这里我们启动一个线程,告诉 View 多久我们会重新绘画自己(还不是为了让那三个指针动起来!)

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
refreshThread.interrupt();
}

这个呢,就是当系统移除我们 View 的时候回到的方法,我们结束掉那个线程,避免浪费资源!

    到这里,我们简单描述了如何绘制一个表的过程。正如我开始说的,这里少了很多细节,比如:我要根据用户的参数来进行数字的大小啦,圆圈的宽度啦,wrap_content的适配啦,有的表秒针一直在连贯的动,那种怎么实现等等,需要大家再看完我简单的分析之后,花点时间改一改我的代码,运行一下就有所体会了。

    国际惯例,github 工程地址:https://github.com/DreamContribution/ClockView

    View 的地址单独放出来吧,方便直接看:https://github.com/DreamContribution/ClockView/blob/master/app/src/main/java/com/duke/clockview/ClockView.java


   最终效果图:

   

QQ20151210-0@2x.png

   最后唠叨一句:这东西呢就是一个练习的 Demo,很说明问题,但是并不代表你不会就末日了,毕竟一个 View 的编写是需要考虑很多问题的,使用成熟的 View 是我们的第一选择!请务必轻松的学,开心的生活,编码...就是一个人生经历。

23 18

共收到18条回复

qq_石少聪 949天前 #1楼

不错不错,需求正好让做一个这样的东西,学习了

2 评论

qq_8hn10k5w 949天前 #2楼

还行界面漂亮。....

2 评论

qq_樱海坊主 949天前 #3楼

写得很好,不错不错。

2 评论

叫我小刘 949天前 #4楼

牛逼。66666666666666666666666

2 评论

叫我小刘 949天前 #5楼

牛逼。66666666666666666666666

2 评论

qq_"也好" 949天前 #6楼

牛逼。66666666666666666666666
6飞了

2 评论

shortleg 949天前 #7楼

66666666666666666666666666666666666

2 评论

靠谱儿活动君 949天前 #8楼

赞~感谢分享~有事儿没事儿来两篇~~

0 评论

qq_kbqt7srd 949天前 #9楼

写的不错,给个好评。。。。。。

1 评论

qq_7q4wt618 949天前 #10楼

66666666666666666666666666666666666

0 评论

qq_5wjm3sfv 949天前 #11楼

66666赞一个。这下够了吧

0 评论

jike_8689679 949天前 #12楼

我也来揍热闹,行了吧

0 评论

weibo_m914bh0e 945天前 #13楼

赞一个, 写的挺好66666

0 评论

weibo_m914bh0e 945天前 #14楼

赞一个, 写的挺好66666

0 评论

yangwei2015 944天前 #15楼

屌~!希望继续进阶~!

0 评论

郝锡强 941天前 #16楼

写的不错,就是时间有点久

0 评论

AigeStudio 938天前 #17楼

1.建议将一些重复运算提取出来

2.不建议使用interrupt方法终止线程,还是配合标志位安全些,最好的办法是迫使线程抛出异常

0 评论

cntnn11 937天前 #18楼

从立项到查看最终效果,

每一步都很详细,这才算一个很好的干货。

0 评论

加入小组与大家一起讨论吧