17、Android之SurfaceView实例自定义SurfaceView的应用——小球跟着手指移动

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

实现的思路:1)自定义SurfaceView,在新线程中每间隔0.1秒就调用一次绘图方法;2)重写自定义SurfaceView的onTouchEvent方法,记录触屏坐标,用新的坐标重新绘制小球。

关键技术点:自定义SurfaceView应用、触摸事件处理、canvas绘图、Paint应用


第一步:新建一个工程,命名为BallSurfaceViewDemo,Activity命名为BallActivity。

第二步:编写自定义SurfaceView类BallSurfaceView,本例中将BallSurfaceView作为BallActivity的内部类,BallActivity代码如下:

  1. package com.zyg.surfaceview.ball;  
  2.   
  3. import java.util.Random;  
  4.   
  5. import android.app.Activity;  
  6. import android.content.Context;  
  7. import android.graphics.Canvas;  
  8. import android.graphics.Color;  
  9. import android.graphics.Paint;  
  10. import android.os.Bundle;  
  11. import android.view.MotionEvent;  
  12. import android.view.SurfaceHolder;  
  13. import android.view.SurfaceHolder.Callback;  
  14. import android.view.SurfaceView;  
  15. import android.view.Window;  
  16. import android.view.WindowManager;  
  17.   
  18. public class BallActivity extends Activity {  
  19.     @Override  
  20.     public void onCreate(Bundle savedInstanceState) {  
  21.         super.onCreate(savedInstanceState);  
  22.         //设置全屏   
  23.         requestWindowFeature(Window.FEATURE_NO_TITLE);  
  24.         this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,  
  25.                 WindowManager.LayoutParams.FLAG_FULLSCREEN);  
  26.           
  27.         setContentView(new BallSurfaceView(this));  
  28.     }  
  29.       
  30.     class BallSurfaceView extends SurfaceView implements Callback,Runnable{  
  31.         private int screenW;        //屏幕宽度   
  32.         private int screenH;        //屏幕高度   
  33.         private Paint paint;        //定义画笔   
  34.         private float cx = 50;      //圆点默认X坐标   
  35.         private float cy = 50;      //圆点默认Y坐标   
  36.         private int radius = 20;  
  37.         //定义颜色数组   
  38.         private int colorArray[] = {Color.BLACK,Color.BLACK,Color.GREEN,Color.YELLOW, Color.RED};  
  39.         private int paintColor = colorArray[0]; //定义画笔默认颜色   
  40.         private Canvas canvas = null//定义画布   
  41.         private Thread th = null;     //定义线程   
  42.         private SurfaceHolder sfh = null;  
  43.           
  44.         public BallSurfaceView(Context context){  
  45.             super(context);  
  46.             /*备注1:在此处获取屏幕高、宽值为0,以为此时view还未被创建, 
  47.              * 在接口Callback的surfaceCreated方法中view才被创建 
  48.              */  
  49.             /*screenW = getWidth(); 
  50.             screenH = getHeight();*/  
  51.               
  52.             //初始化画笔   
  53.             initPaint();  
  54.             sfh = getHolder();  
  55.             sfh.addCallback(this);  
  56.             th = new Thread(this);  
  57.         }  
  58.           
  59.         @Override  
  60.         public void surfaceCreated(SurfaceHolder holder) {  
  61.             //获取屏幕宽度   
  62.             screenW = getWidth();  
  63.             //获取屏幕高度   
  64.             screenH = getHeight();  
  65.             //启动绘图线程   
  66.             th.start();  
  67.         }  
  68.           
  69.         private void initPaint(){  
  70.             paint = new Paint();  
  71.             //设置消除锯齿   
  72.             paint.setAntiAlias(true);  
  73.             //设置画笔颜色   
  74.             paint.setColor(paintColor);  
  75.         }  
  76.           
  77.         @Override  
  78.         public void run() {  
  79.             while(true){  
  80.                 try{  
  81.                     myDraw();   
  82.                     Thread.sleep(100);  
  83.                 }catch(InterruptedException e){  
  84.                     e.printStackTrace();  
  85.                 }  
  86.             }  
  87.         }  
  88.           
  89.         /*备注2:切记,在自定SurfaceView中定义的myDraw方法,自定义View(继承自View的子类)中的onDraw方法 
  90.          * 完全是两码事: 
  91.          * 1)自定义View(继承自View的子类)中的onDraw方法是重写父类的onDraw方法,在调用postInvalidate后会自动回调该onDraw()方法。 
  92.          * 2)此处的myDraw方法需要手动调用,所以此处故意将方法命名为myDraw,突出为该方法是自己写的,非重写父类的方法 。 
  93.          *  
  94.          */  
  95.         //重写onDraw方法实现绘图操作   
  96.         protected void myDraw() {  
  97.             //获取canvas实例   
  98.             canvas = sfh.lockCanvas();  
  99.             //将屏幕设置为白色   
  100.             canvas.drawColor(Color.WHITE);  
  101.             //修正圆点坐标   
  102.             revise();  
  103.             //随机设置画笔颜色   
  104.             setPaintRandomColor();  
  105.             //绘制小圆作为小球   
  106.             canvas.drawCircle(cx, cy, radius, paint);  
  107.             //将画好的画布提交   
  108.             sfh.unlockCanvasAndPost(canvas);  
  109.         }  
  110.           
  111.         //为画笔设置随机颜色   
  112.         private void setPaintRandomColor(){  
  113.             Random rand = new Random();  
  114.             int randomIndex = rand.nextInt(colorArray.length);  
  115.             paint.setColor(colorArray[randomIndex]);  
  116.         }  
  117.           
  118.         //修正圆点坐标   
  119.         private void revise(){  
  120.             if(cx <= radius){  
  121.                 cx = radius;  
  122.             }else if(cx >= (screenW-radius)){  
  123.                 cx = screenW-radius;  
  124.             }  
  125.             if(cy <= radius){  
  126.                 cy = radius;  
  127.             }else if(cy >= (screenH-radius)){  
  128.                 cy = screenH-radius;  
  129.             }  
  130.         }  
  131.           
  132.         @Override  
  133.         public boolean onTouchEvent(MotionEvent event) {  
  134.             switch (event.getAction()) {  
  135.             case MotionEvent.ACTION_DOWN:  
  136.                 // 按下   
  137.                 cx = (int) event.getX();  
  138.                 cy = (int) event.getY();  
  139.                 break;  
  140.             case MotionEvent.ACTION_MOVE:  
  141.                 // 移动   
  142.                 cx = (int) event.getX();  
  143.                 cy = (int) event.getY();  
  144.                 break;  
  145.             case MotionEvent.ACTION_UP:  
  146.                 // 抬起   
  147.                 cx = (int) event.getX();  
  148.                 cy = (int) event.getY();  
  149.                 break;  
  150.             }  
  151.               
  152.             /* 
  153.              * 备注1:次处一定要将return super.onTouchEvent(event)修改为return true,原因是: 
  154.              * 1)父类的onTouchEvent(event)方法可能没有做任何处理,但是返回了false。 
  155.              * 2)一旦返回false,在该方法中再也不会收到MotionEvent.ACTION_MOVE及MotionEvent.ACTION_UP事件。 
  156.              */  
  157.             //return super.onTouchEvent(event);   
  158.             return true;    
  159.         }  
  160.           
  161.         @Override  
  162.         public void surfaceChanged(SurfaceHolder holder, int format, int width,  
  163.                 int height) {  
  164.               
  165.         }  
  166.           
  167.         @Override  
  168.         public void surfaceDestroyed(SurfaceHolder holder) {  
  169.               
  170.         }  
  171.           
  172.     }  
  173. }  

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就可以。


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 AndroidSurfaceView 中实现倒计时功能,您需要创建一个线程,该线程在 SurfaceView 上绘制计时器并更新计时器的值。以下是一个简单的示例,您可以参考: ```java public class CountdownSurfaceView extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder mHolder; private CountdownThread mThread; private int mCountdownValue = 10; // 倒计时的初始值 public CountdownSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); mHolder = getHolder(); mHolder.addCallback(this); } @Override public void surfaceCreated(SurfaceHolder holder) { mThread = new CountdownThread(mHolder, mCountdownValue); mThread.start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { boolean retry = true; mThread.setRunning(false); while (retry) { try { mThread.join(); retry = false; } catch (InterruptedException e) { // 重试 } } } private class CountdownThread extends Thread { private SurfaceHolder mThreadHolder; private int mCountdownValue; private boolean mIsRunning = true; public CountdownThread(SurfaceHolder holder, int countdownValue) { mThreadHolder = holder; mCountdownValue = countdownValue; } public void setRunning(boolean isRunning) { mIsRunning = isRunning; } @Override public void run() { while (mIsRunning) { Canvas canvas = null; try { canvas = mThreadHolder.lockCanvas(); synchronized (mThreadHolder) { drawCountdown(canvas); Thread.sleep(1000); // 每秒钟更新一次计时器 mCountdownValue--; if (mCountdownValue == 0) { break; // 计时器结束 } } } catch (InterruptedException e) { // 重试 } finally { if (canvas != null) { mThreadHolder.unlockCanvasAndPost(canvas); } } } } private void drawCountdown(Canvas canvas) { Paint paint = new Paint(); paint.setColor(Color.WHITE); paint.setTextSize(100); canvas.drawColor(Color.BLACK); canvas.drawText(String.valueOf(mCountdownValue), getWidth() / 2, getHeight() / 2, paint); } } } ``` 在这个例子中,我们创建了一个 CountdownSurfaceView 类,它继承自 SurfaceView,并实现了 SurfaceHolder.Callback 接口。在 surfaceCreated() 方法中,我们创建了一个 CountdownThread 线程,并启动该线程。在 CountdownThread 线程中,我们使用 Canvas 绘制计时器,并使用 Thread.sleep() 方法使线程休眠一秒钟。每次线程醒来时,我们更新计时器的值,并检查计时器是否结束。如果计时器结束,我们退出线程。最后,在 surfaceDestroyed() 方法中,我们停止线程并等待线程结束。 请注意,在这个例子中,我们将计时器的值绘制在 SurfaceView 的中心。您可以根据自己的需要更改这个位置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值