实现的功能:手指在屏幕上滑动,变幻颜色的小球始终跟随手指移动。
实现的思路: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;
- private float cy = 50;
- 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);
-
-
-
-
-
-
-
- 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();
- }
- }
- }
-
-
-
-
-
-
-
-
- protected void myDraw() {
-
- 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;
- }
-
-
-
-
-
-
-
- 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就可以。