HardBirch

【Android应用实例之三】跟随手指的小球——自定义SurfaceView应用

时间:11-09-27 栏目:iOS移动应用开发技术 作者:张飞不张,文采横飞 评论:0 点击: 1,235 次

 

实现的功能:手指在屏幕上滑动,变幻颜色的小球始终跟随手指移动。

实现的思路: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就可以。


【Android应用实例之三】跟随手指的小球——自定义SurfaceView应用:等您坐沙发呢!

发表评论


QQ群互动

Linux系统与内核学习群:194051772

WP建站技术学习交流群:194062106

魔豆之路QR

魔豆的Linux内核之路

魔豆的Linux内核之路

优秀工程师当看优秀书籍

优秀程序员,要看优秀书!

赞助商广告

友荐云推荐