实现的功能:手指在屏幕上滑动,变幻颜色的小球始终跟随手指移动。
实现的思路:1)自定义SurfaceView,在新线程中每间隔0.1秒就调用一次绘图方法;2)重写自定义SurfaceView的onTouchEvent方法,记录触屏坐标,用新的坐标重新绘制小球。
关键技术点:自定义SurfaceView应用、触摸事件处理、canvas绘图、Paint应用
第一步:新建一个工程,命名为BallSurfaceViewDemo,Activity命名为BallActivity。
第二步:编写自定义SurfaceView类BallSurfaceView,本例中将BallSurfaceView作为BallActivity的内部类,BallActivity代码如下:
- package com.zyg.surfaceview.ball;
- import java.util.Random;
- import android.app.Activity;
- import android.content.Context;
- import android.graphics.Canvas;
- import android.graphics.Color;
- import android.graphics.Paint;
- import android.os.Bundle;
- import android.view.MotionEvent;
- import android.view.SurfaceHolder;
- import android.view.SurfaceHolder.Callback;
- import android.view.SurfaceView;
- import android.view.Window;
- import android.view.WindowManager;
- public class BallActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- //设置全屏
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
- WindowManager.LayoutParams.FLAG_FULLSCREEN);
- setContentView(new BallSurfaceView(this));
- }
- class BallSurfaceView extends SurfaceView implements Callback,Runnable{
- private int screenW; //屏幕宽度
- private int screenH; //屏幕高度
- private Paint paint; //定义画笔
- private float cx = 50; //圆点默认X坐标
- private float cy = 50; //圆点默认Y坐标
- private int radius = 20;
- //定义颜色数组
- private int colorArray[] = {Color.BLACK,Color.BLACK,Color.GREEN,Color.YELLOW, Color.RED};
- private int paintColor = colorArray[0]; //定义画笔默认颜色
- private Canvas canvas = null; //定义画布
- private Thread th = null; //定义线程
- private SurfaceHolder sfh = null;
- public BallSurfaceView(Context context){
- super(context);
- /*备注1:在此处获取屏幕高、宽值为0,以为此时view还未被创建,
- * 在接口Callback的surfaceCreated方法中view才被创建
- */
- /*screenW = getWidth();
- screenH = getHeight();*/
- //初始化画笔
- initPaint();
- sfh = getHolder();
- sfh.addCallback(this);
- th = new Thread(this);
- }
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- //获取屏幕宽度
- screenW = getWidth();
- //获取屏幕高度
- screenH = getHeight();
- //启动绘图线程
- th.start();
- }
- private void initPaint(){
- paint = new Paint();
- //设置消除锯齿
- paint.setAntiAlias(true);
- //设置画笔颜色
- paint.setColor(paintColor);
- }
- @Override
- public void run() {
- while(true){
- try{
- myDraw();
- Thread.sleep(100);
- }catch(InterruptedException e){
- e.printStackTrace();
- }
- }
- }
- /*备注2:切记,在自定SurfaceView中定义的myDraw方法,自定义View(继承自View的子类)中的onDraw方法
- * 完全是两码事:
- * 1)自定义View(继承自View的子类)中的onDraw方法是重写父类的onDraw方法,在调用postInvalidate后会自动回调该onDraw()方法。
- * 2)此处的myDraw方法需要手动调用,所以此处故意将方法命名为myDraw,突出为该方法是自己写的,非重写父类的方法 。
- *
- */
- //重写onDraw方法实现绘图操作
- protected void myDraw() {
- //获取canvas实例
- canvas = sfh.lockCanvas();
- //将屏幕设置为白色
- canvas.drawColor(Color.WHITE);
- //修正圆点坐标
- revise();
- //随机设置画笔颜色
- setPaintRandomColor();
- //绘制小圆作为小球
- canvas.drawCircle(cx, cy, radius, paint);
- //将画好的画布提交
- sfh.unlockCanvasAndPost(canvas);
- }
- //为画笔设置随机颜色
- private void setPaintRandomColor(){
- Random rand = new Random();
- int randomIndex = rand.nextInt(colorArray.length);
- paint.setColor(colorArray[randomIndex]);
- }
- //修正圆点坐标
- private void revise(){
- if(cx <= radius){
- cx = radius;
- }else if(cx >= (screenW-radius)){
- cx = screenW-radius;
- }
- if(cy <= radius){
- cy = radius;
- }else if(cy >= (screenH-radius)){
- cy = screenH-radius;
- }
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- // 按下
- cx = (int) event.getX();
- cy = (int) event.getY();
- break;
- case MotionEvent.ACTION_MOVE:
- // 移动
- cx = (int) event.getX();
- cy = (int) event.getY();
- break;
- case MotionEvent.ACTION_UP:
- // 抬起
- cx = (int) event.getX();
- cy = (int) event.getY();
- break;
- }
- /*
- * 备注1:次处一定要将return super.onTouchEvent(event)修改为return true,原因是:
- * 1)父类的onTouchEvent(event)方法可能没有做任何处理,但是返回了false。
- * 2)一旦返回false,在该方法中再也不会收到MotionEvent.ACTION_MOVE及MotionEvent.ACTION_UP事件。
- */
- //return super.onTouchEvent(event);
- return true;
- }
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width,
- int height) {
- }
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- }
- }
- }
main.xml与AndroidManifest.xml未作修改,不再贴出~
备注:
备注1介绍了在BallSurfaceView中获取屏幕宽度和高度的位置以及原因,详见代码。
备注2介绍了onTouchEvent方法在实际开发中的一个Bug的解决方法,详见代码。
第三步:运行程序,效果如下:
总结1:自定义SurfaceView的应用(总结内容来自网络,稍作整理修改)
1)继承SurfaceView类并实现SurfaceHolder.Callback接口
2)SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View,其接口有:
surfaceCreated(SurfaceHolderholder):当Surface第一次创建后会立即调用该方法。可以在该方法中做一些与绘制界面相关的初始化工作,一般情况下都是在新开的的线程来绘制界面。
surfaceChanged(SurfaceHolderholder, int format, int width,int height):当Surface的状态(大小和格式)发生变化的时候会调用该方法,在surfaceCreated调用后该方法数至少会被调用一次。
3)SurfaceHolder 类:用于控制surface的接口,它提供了控制surface 的大小,格式,上面的像素。
SurfaceView的getHolder()方法可以获取SurfaceHolder对象,Surface 就在SurfaceHolder对象内。虽然Surface保存了当前窗口的像
素数据,但是在使用过程中不直接和Surface打交道,而是由SurfaceHolder的 lockCanvas()方法来获取Canvas对象,通过在Canvas
上绘制内容来修改Surface中的数据。如果Surface不可编辑或则尚未创建调用该方法会返回null,在 unlockCanvas() 和 lockCanvas()
中Surface的内容是不缓存的,所以需要完全重绘Surface的内容,为了提高效率只重绘变化的部分则可以调用lockCanvas(Rectrect)
函数来指定一个rect区域,这样该区域外的内容会缓存起来。在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的
一个同步锁直到调用unlockCanvasAndPost(Canvascanvas)函数才释放该锁,这里的同步机制保证在Surface绘制过程中不会被改变
(被摧毁、修改)等。
总结2:View与SurfaceView区别及应用场景(总结内容来自网络,稍作整理修改)
1)SurfaceView是View的子类。
2)View缺乏爽缓冲机制,当程序需要更新View上的图像时,程序必须重绘View上显示的整张图片。而SurfaceView类具有双缓冲机制。还可以通过CanvaslockCanvas(Rect dirty)锁定SurfaceView上的Rect划分的区域,获取该Surface上的局部Canvas,只更新局部Canvas,效率会高很多 。(对应的实例后面的文章会介绍)
3)本质区别:SurfaceView是在一个新起的单独线程中可以重新绘制画面,而View必须在UI的主线程中更新画面。
在UI的主线程中更新画面可能会引发问题,比如更新画面的时间过长,那么主UI线程可能会被正在绘图的方法阻塞。那么将无法响应按键,触屏等消息。
使用surfaceView 是在新的线程中更新画面,所以不会阻塞你的UI主线程。但也引发了另外一个问题,就是事件同步。比如触屏了一下,需要SurfaceView中thread处理,一般就需要有一个event queue的设计来保存touch event,涉及到线程同步,又变得比较复杂。
4)应用场景:如果程序或者游戏界面的动画元素较多,而且很多都需要通过定时器(主动更新View)来控制这些动画元素的移动,最好考虑使用SurfaceView,而不是View。
如果程序界面元素是通过按键、触摸、点击按钮(被动更新View)等控制动画元素,更新频率较低,采用View就可以。