详解与重构hyman《Android SurfaceView实战 打造抽奖转盘》
作者:邵励治
一、概述——关于SurfaceView您不得不知道的二三事
1.SurfaceView是干什么的?
答:SurfaceView就像一块画板,我们可以在上面即时的绘画。
2.SurfaceView的优势?
答:相信您注意到了上文中“即时”二字被重点标出。如果您写过自定义View的话,会发现想让画面动起来需要调用如下两个方法让界面重绘:
- invalidate:于UI线程中使用
- postinvalidate:于非UI线程中使用
当上面两个方法使用太频繁的时候,我们的Android系统的UI界面就有可能阻塞。而您肯定不想让您的UI界面阻塞————毕竟卡死五秒就崩出去了(而且我一秒都不想卡)
而我们伟大的SurfaceView,可以做到随着数据被更新即时的改变UI界面,而不需要调用上面两个方法,因而也就不会造成UI阻塞。
(注:你们没发现上面的这些话其实什么都没说吗?原因是我并不知道为什么不会造成UI阻塞,但我也不想深究,嘻嘻)
3.SurfaceView的周边知识
- “深度优先遍历文章学习法”————我就是用我自创的这个学习法遍历Hyman这篇文章的。
优点:
(1) 可以了解作者所说的全部内容
(2) 可以迅速构建起一个知识体系
缺点:
(1) 效率低下(老板需要你迅速写出一个抽奖转盘用来救他落水的女儿,那你用我这招肯定不行)
(2) 民科感及其、超级、很浓重————主要赖我起的这名儿 - Android中UI框架的基本概念
您听说过DecorView吗?您了解PhoneWindow吗?您关心Activity#setContentView是如何工作的吗?
如果您感兴趣,请到文末列出的三十多篇参考文献中寻找您想要的内容。 - Android自定义View
如果您是一个Android初学者,那您一定不应该错过这里的内容,去学习一下吧。也许学习完了您就能感受到了SurfaceView的厉害之处了。(正如我写了三叠子数学作业纸的电磁学题后才能感受到麦克斯韦方程的美一样。)
二、新建Java文件——LotteryTurntable(中文意思:抽奖转盘)
1. LotterTurntable类继承关系
extends:SurfaceView
implements SurfaceHolder.Callback
2. LotterTurntable的构造方法
原因:
(1) 若想在XML布局文件中引用我们的自定义View——LotteryTurntable的话,LotteryTurntable至少需要两个参数的构造方法。
(2) 为了实现无论LotteryTurntable的哪一个构造方法被调用都会执行同样操作的伟大梦想
3. 重写(Override)SurfaceHolder.Callback接口中的三个抽象方法
原因:
(1) 您就是想不重写(Override)接口的抽象方法也不行呀
(2) 为什么不行的原因在《Java编程思想》接口定义的章节中
三、SurfaceView起手式
1.何为SurfaceView的起手式?
答:如同下象棋讲究当头炮,打篮球讲究三威胁。我们在使用SurfaceView的时候也需要起手式————即代码上的起手式。
虽然我认为这种类似于“起手式”的形式在代码上运用属于一种重复劳作————既然每个人写的时候都需要写这些代码,那么为什么不把这些代码封装起来呢?
但是像这么做无异于获得了更好的灵活性————就像我们公司最灵活的程序员编程只用0、1两个按键。
2.起手式的具体内容
字段的声明:
构造方法:获取surfaceHolder,并添加回调结构
surfaceCreated方法:打开绘图开关,开启线程
BUG提示:
我上次把在surfaceCreated中做初始化工作的代码写在了上述两行代码的下面,结果我的程序成了六脉神剑————时灵时不灵。
原因是,当上述两行代码执行后,子线程就开始工作,程序就成为了多线程的————一个线程负责初始化工作(也就是主线程),一个线程负责绘图(也就是子线程),它们是同时进行的。
那么,是先完成初始化工作,还是先进行绘图操作可就说不准了。当先完成初始化工作的时候,程序就跟正常的表现一样。但当先完成绘图操作时,程序就会因为传入未初始化的实例而崩溃————程序成了定时炸弹,不知道什么时候会炸。
所以,我们一定要先让主线程跑完————执行完初始化操作之后,再执行开启子线程的代码drawingSwitch=true;和thread.start();。这样,绘图时就一定不会因为传入未初始化的实例而让程序崩溃了。
历史遗留问题:
值得一提的是,我当时是使用drawBitmap(bitmap,null,rect,null);这条语句时,bitmap未初始化造成的程序崩溃。但是,Android Studio却没有弹出任何的错误信息,这就让我感到十分难受,无从Debug。最后我用的单元测试大法————一个一个变量的Sytem.out.println + 注释,才定位到原来是bitmap=null造成的问题。我目前还不知道这是什么原因造成的不提示错误信息。surfaceDestroyed方法:关闭绘图开关(并不关闭线程,浅显原因是Thread.stop方法由于安全原因被禁用了,深刻原因请自行查询)
子线程的声明与代码体(很关键,因为您需要在此进行使用canvas绘画)
3.关于起手式的周边问题
Surface是什么?
答:Surface是原始图像缓冲区的一个句柄,而原始图像是由屏幕图像合成器管理的。当得到一个Surface对象时,同时会得到一个Canvas对象。因为Surface定义了一个Canvas成员变量(Surface.java的90行)句柄是什么?
答:个人理解(此处个人指邵励治本人),句柄可以理解为遥控器、方向盘等等。简单说就是你用句柄可以操作该句柄对应的对象。Surface实现的Parcelable接口是做什么的?
答:一两句说不清楚,请看《Android开发之Parcelable使用详解》与《深入理解Java对象序列化》两篇文章,我没记错的话,都是CSDN上的。SurfaceView与Surface的联系?
答:surface是管理显示内容的数据,SurfaceView就是用来把这些数据显示出来到屏幕上面的窗口。SurfaceHolder是什么?
答:SurfaceHolder是surface的一个抽象接口,可以通过SurfaceHolder来控制Surface。SurfaceHolder.Callback是什么?
答:SurfaceHolder.Callback是监听Surface改变的一个接口,里面有四个重要方法:
(1) SurfaceHolder = surfaceView.getHolder();
(2) Surface = SurfaceHolder.getSurface();
(3) Canvas = SurfaceHolder.lockCanvas(Rect rect);
(4) Canvas = Surface.lockCanvas(Rect rect);
四、开始您的绘画吧————设计绘画工具有:Canvas、Paint、Bitmap、Rect、RectF等
1.来绘图吧
- 原型图
- 原型图分析
原型图由以下几个元素构成
(1)五个扇形(特点:只用黑笔描出边框)
(2)五段文字(特点:文字是随扇形的曲率而弯曲的,并且文字的位置相对于扇形是居中的
(3)五张图片(特点:五张图片始终是正方向的)
(4)一个向上的箭头(点击它整个圆盘会旋转)
2.绘图的原理
我们基于什么理念画扇形?
答:我们基于单位圆坐标轴画扇形(高中数学中经常提到的单位圆诸位还记得吗?就是学三角函数、弧度制那会儿介绍的),只不过这单位圆跟咱们学过的那个是反着的
函数:canvas.drawArc(RectF, 初始角度(float,角度值),末尾角度(float,角度值),是否滑出圆弧与圆心连线(Boolean)&#x