自学了android有几个月了,跟着网上的节奏,应该早点写些博客来提高自己的水准的。但苦于技术水准始终不自信(也是不过关的结果吧),就一直只是将自己学习过程中的问题和重要的知识点写在自己的笔记文档中。
但,总感觉一个人写下来成就感还是欠缺了那么一些,而且有些问题及解答方法抛出来,是有可能得到更多好的反馈及解决方案的。于是,本着不作不会死的心态,一步一步在技术成长的道路前行——>这篇博客就是其中一步!
若博客中有些技术知识点有误或者有更优化的解答方案,还望各位小伙伴多多指出。
以下是正题了:
目标:利用SurfaceView实现一个简单的计时器
图示:
描述:1.利用SurfaceView来实现计时功能,同时不断将圆弧画满;2.点击按钮可以停止计时;
重点:
1.自定义SurfaceView中针对SurfaceHolder.CallBack的三个方法进行覆写;
2.通过surfaceHolder.lockCanvas()在新开的线程中得到canvas对象,从而进行图形和时间文字的绘制;
3.通过设置flag值,从而控制在线程run()方法中逻辑代码的执行;
重要部分代码:
public TestView(Context context) {
super(context);
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
countThread = new CountThread(surfaceHolder);
}
public TestView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
countThread = new CountThread(surfaceHolder);
}
以上为自定义的SurfaceView(TestView)的构造函数,做相应的初始化工作。(第二个构造方法在实现过程中没有覆写,导致如果是通过布局文件引入进Activity中时,则显示不出View——>也就是一般自定义View一定要覆写的构造方法)。
在构造方法中初始化了自定义的内部线程类CountThread,用来执行绘制工作。
以下为CountThread类的run()方法执行逻辑:
@Override
public void run() {
Canvas canvas = null;
int pivotX = getResources().getDisplayMetrics().widthPixels / 2;
RectF rectF = new RectF(pivotX - 300, pivotX - 300, pivotX + 300, pivotX + 300);
while (!isStop) {
try {
canvas = surfaceHolder.lockCanvas();
canvas.drawColor(Color.WHITE);//设置画布背景为白色
// canvas.drawRoundRect(300, 300, 600, 600, 150, 150, paint);//直接使用该行代码来画圆是行不通的,因为这个方法要求版本21,我的手机运行android版本是19
canvas.drawArc(rectF, -90, endAngle++, false, paint);//-90在这里不等于270,所以要想从最上方开始画弧,就得用-90
canvas.drawText(countTime(endAngle), pivotX, pivotX, paintText);//显示计算的时间
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (canvas != null) {//需要对canvas进行非空判断
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
主要就是通过Thread.sleep(1000)来停顿一秒,从而通过endAngle来计数。
至此,基本的程序逻辑已经实现。再讲该自定义View引入进Activity指定的layout布局文件中即可进行显示。
但,这其中也会涉及到相应的问题。譬如:
1.图示中的按钮无法显示出来,只能看到自定义的SurfaceView的视图;
我的做法是:将布局设定为FrameLayout即可。因为SurfaceView是浮在窗口的一层,那么就可以把它看做一个图层。
2.java.lang.IllegalThreadStateException:Thread already started
该错误表明,线程已经存在了。这种错误的操作重现是:按下home键或者menu键会导致该自定的SurfaceView销毁,但线程并没有被销毁,再次启动该自定义SurfaceView的时候又去重新启动该线程。解决的方法是:
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (!countThread.isAlive()) {//如果线程不存在,则启动线程——>当应用挂起的时候Thread是存在的,如果不做这个判断,会报“Thread already started ”错误
countThread.start();//SurfaceView创建时开启线程
}
}
在surfaceCreated()方法中对该线程是否是在存活中进行判断。
当然,这里面还有最重要的一个问题:
当按下home键或者menu键时,程序是没有在计时的。那么这种情况下,我的一个解决方案是,通过开启一个service来接收程序停止( onStop() )时已经计时的数值,然后传递给service记下并计时,当应用程序界面重新回归屏幕时( onRestart() )则将数据取出并回传到自定义的SurfaceView的逻辑run()方法中继续计时。
整个小程序的代码可以通过以下链接下载:
点击进入下载页面:http://xiazai.jb51.net/201701/yuanma/AndroidSurfaceView(jb51.net).rar