当前位置: 首页 > article >正文

SurfaceView 基本使用

SurfaceView 基本使用

       Android中提供了View进行绘图处理,View可以满足大部分的绘图需求,但是有时候,View却显得力不从心,所以Android提供了SurfaceView给Android开发者,以满足更多的绘图需求。下面就让我们一起来了解一下SurfaceView。

  1. SurfaceView的作用

        View是通过刷新来重绘视图,Android 系统通过发出VSYNC信号来进行屏幕的重绘,刷新的时间间隔是16ms,如果我们可以在16ms以内将绘制工作完成,则没有任何问题,如果我们绘制过程逻辑很复杂,并且我们的界面更新还非常频繁,这时候就会造成界面的卡顿,影响用户体验,为此Android提供了SurfaceView来解决这一问题。

       SurfaceView 继承自 View,但拥有独立的绘制表面,它不与其宿主窗口共享同一个绘图表面,可以单独在一个线程进行绘制,并不会占用主线程的资源。这样,绘制就会比较高效,游戏,视频播放,还有最近热门的直播,都可以用SurfaceView来实现。SurfaceView 有两个子类 GLSurfaceView 和 VideoView。

       SurfaceViewView的区别:

       1)View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁地刷新。

       2)View在主线程中对画面进行刷新,而SurfaceView通常会通过一个子线程来进行页面的刷新。

       3)View在绘图时没有使用双缓冲机制,而SufaceView在底层实现机制中就已经实现了双缓冲机制。

 

     2. SurfaceView的使用

       使用 SurfaceView 基本可以按照三个步骤来实现,分别是:创建、初始化、使用。下面我们分别来介绍着三个步骤。

      2.1 创建SurfaceView

       我们需要自定义一个类MySurfaceView 继承自SurfaceView,并实现两个接口SurfaceHolder.CallBackRunnable,代码如下所示:

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable {public MySurfaceView(Context context) {this(context, null);}public MySurfaceView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}// SurfaceView 创建时调用@Overridepublic void surfaceCreated(SurfaceHolder holder) {}// SurfaceView 改变时调用@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}// SurfaceView 销毁时调用@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {}@Overridepublic void run() {}
}

       SurfaceVHolder.Callback 的三个方法分别在SurfaceView创建、改变、销毁的时候进行调用,Runnalbe 的run()方法中写我们子线程中执行的绘图逻辑即可。

     2.2 初始化SurfaceView

       在自定义的SurfaceView中,通常需要定义3个成员变量:

       1)SurfaceHolder mSurfaceHolder 可以控制SurfaceView的大小,格式,可以监控或者改变SurfaceView。

       2)Canvas mCanvas 画布

       3)boolean isDrawing 子线程标志位,用来控制子线程。

       初始化SurfaceView主要就是定义上述三个成员变量并初始化SurfaceHolder并注册对应的回到方法,代码如下所示:

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {private SurfaceHolder mSurfaceHolder;private Canvas mCanvas;private boolean mIsDrawing;...public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}/*** 初始化*/private void init() {// 获取 SurfaceHolder对象mSurfaceHolder = getHolder();//注册 SurfaceHoldermSurfaceHolder.addCallback(this);// 设置可以获取焦点setFocusable(true);// 设置触摸可以获取焦点setFocusableInTouchMode(true);// 保持屏幕常亮this.setKeepScreenOn(true);}...
}

     2.3 使用SurfaceView

       使用SurfaceView也可以分为三个步骤:

       1)通过lockCanvas()方法获得Canvas对象。

       2) 在子线程中使用Canvas对象进行绘制。

       3)使用unlockCanvasAndPost()方法将画布内容进行提交。

       注意: lockCanvas() 方法获得的Canvas对象仍然是上次绘制的对象,由于我们是不断进行绘制,但是每次得到的Canvas对象都是第一次创建的Canvas对象。

       绘制要充分利用SurfaceView的三个回调方法,在surfaceCreate()方法中开启子线程进行绘制。在子线程中,使用一个while(isDrawing)循环来不停地绘制。具体的绘制过程,由lockCanvas()方法进行绘制,并通过unlockCanvasAndPost(mCanvas)进行画布内容的提交。

       实现代码如下所示:

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {private SurfaceHolder mSurfaceHolder;private Canvas mCanvas;private boolean mIsDrawing;public MySurfaceView(Context context) {this(context, null);}public MySurfaceView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}/*** 初始化*/private void init() {// 获取 SurfaceHolder对象mSurfaceHolder = getHolder();//注册 SurfaceHoldermSurfaceHolder.addCallback(this);// 设置可以获取焦点setFocusable(true);// 设置触摸可以获取焦点setFocusableInTouchMode(true);// 保持屏幕常亮this.setKeepScreenOn(true);}// SurfaceView 创建时调用@Overridepublic void surfaceCreated(SurfaceHolder holder) {mIsDrawing = true;// 通过线程池来执行Executors.newCachedThreadPool().execute(this);}// SurfaceView 改变时调用@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}// SurfaceView 销毁时调用@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {mIsDrawing = false;}@Overridepublic void run() {while (mIsDrawing) {drawing();}}private void drawing() {try {// 获取Canvas 画布mCanvas = mSurfaceHolder.lockCanvas();// 下面进行内容的绘制...} finally {if (mCanvas != null) {mSurfaceHolder.unlockCanvasAndPost(mCanvas);}}}
}

   mSurfaceHolder.unlockCanvasAndPost(mCanvas)将这行代码放入finally代码块中,目的是为了确保内容都能够被提交。

     3. 使用案例

       自定义SurfaceView 实现一个简易画板,初始代码如下所示:

package com.lx.surfaceview.widget;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;import java.util.concurrent.Executors;/*** create by lx* date 2020/8/24.* description:*/
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {private SurfaceHolder mSurfaceHolder;private Canvas mCanvas;private boolean mIsDrawing;// 画笔private Paint mPaint;// 路径private Path mPath;// 上次的坐标private float mLastX, mLastY;public MySurfaceView(Context context) {this(context, null);}public MySurfaceView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}/*** 初始化*/private void init() {// 获取 SurfaceHolder对象mSurfaceHolder = getHolder();//注册 SurfaceHoldermSurfaceHolder.addCallback(this);// 设置可以获取焦点setFocusable(true);// 设置触摸可以获取焦点setFocusableInTouchMode(true);// 保持屏幕常亮this.setKeepScreenOn(true);// 初始化画笔mPaint = new Paint();mPaint.setStrokeWidth(10);mPaint.setColor(Color.BLACK);mPaint.setStyle(Paint.Style.STROKE);mPaint.setAntiAlias(true);// 初始化路径mPath = new Path();}// SurfaceView 创建时调用@Overridepublic void surfaceCreated(SurfaceHolder holder) {mIsDrawing = true;// 通过线程池来执行Executors.newCachedThreadPool().execute(this);}// SurfaceView 改变时调用@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}// SurfaceView 销毁时调用@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {mIsDrawing = false;}@Overridepublic void run() {while (mIsDrawing) {drawing();}}private void drawing() {try {// 获取Canvas 画布mCanvas = mSurfaceHolder.lockCanvas();// 下面进行内容的绘制mCanvas.drawColor(Color.WHITE);mCanvas.drawPath(mPath, mPaint);} finally {if (mCanvas != null) {mSurfaceHolder.unlockCanvasAndPost(mCanvas);}}}@Overridepublic boolean onTouchEvent(MotionEvent event) {// 获取当前触摸事件的坐标float x = event.getX();float y = event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:// 按下mLastX = x;mLastY = y;mPath.moveTo(mLastX, mLastY);break;case MotionEvent.ACTION_MOVE:// 移动float dx = Math.abs(x - mLastX);float dy = Math.abs(y - mLastY);if (dx >= 3 || dy >= 3) {mPath.quadTo(mLastX, mLastY, (mLastX + x) / 2, (mLastY + y) / 2);}mLastX = x;mLastY = y;break;case MotionEvent.ACTION_UP:// 抬起break;}return true;}
}

       运行效果如下图:

     4. 优化

       由于 while (isDrawing)是个死循环,drawing方法一直在执行,就导致一直在绘制,这样会一直占用CPU资源。

       1. 使用sleep休眠,在run方法中进行休眠,代码如下:

    public void run() {while (mIsDrawing) {long start = System.currentTimeMillis();drawing();long end = System.currentTimeMillis();if (end - start < 100) {try {Thread.sleep(100 - (end - start));} catch (InterruptedException e) {e.printStackTrace();}}}}

       使用sleep休眠的方式虽然可以降低CPU占用率,但是每次绘制时都会休眠(100ms - drawing时间),绘制过程中有种不跟手的感觉。

       2. 使用wait和notify方法,当没有绘制时让线程等待,当有绘制时唤醒线程,代码如下所示:

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {...private boolean mIsWait = true;@Overridepublic void run() {drawing();synchronized (MySurfaceView.this) {while (mIsDrawing) {if (mIsWait) {try {MySurfaceView.this.wait();} catch (InterruptedException e) {e.printStackTrace();}}drawing();}}}...@Overridepublic boolean onTouchEvent(MotionEvent event) {// 获取当前触摸事件的坐标float x = event.getX();float y = event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:synchronized (MySurfaceView.this) {mIsWait = false;MySurfaceView.this.notify();}// 按下mLastX = x;mLastY = y;mPath.moveTo(mLastX, mLastY);break;case MotionEvent.ACTION_MOVE:// 移动float dx = Math.abs(x - mLastX);float dy = Math.abs(y - mLastY);if (dx >= 3 || dy >= 3) {mPath.quadTo(mLastX, mLastY, (mLastX + x) / 2, (mLastY + y) / 2);}mLastX = x;mLastY = y;break;case MotionEvent.ACTION_UP:// 抬起mIsWait = true;break;}return true;}
}

       手指落下,将mIsWait设置为false表示不需要等待,并唤醒线程。
       手指离开,将mIsWait设置为true表示需要等待。

http://www.lryc.cn/news/2419494.html

相关文章:

  • 硬件测试需要什么软件是什么原因,什么硬件软件检测温度准啊
  • zeros什么意思_什么是张量?
  • Ubuntu Touch的小确幸(Linux系统手机Ubports)
  • 数据结构(C语言版)--速成笔记【持续更新中。。】
  • MPEG-4视频压缩基础
  • 【FISCO-BCOS】 一、默认配置搭建区块链网络
  • textmate开发一个blog
  • 8个简单有用windows命令,电脑小白的高级命令!
  • MultiByteToWideCharWideCharToMultiByte 函数介绍
  • 机器学习——多项式拟合
  • java6下载安装_windows下如何安装java6.0
  • 使用C#的DrawString方法绘制文本
  • 建筑抗震设计规范
  • java入门基础教程(纯干货知识点+视频资源)
  • CC2530 zigbee模块概述与环境搭建
  • petri网基本概念
  • 【自动控制理论】 相位裕度和增益裕度
  • 数据库,万能密码与密码解析
  • android 通过广播唤醒被杀死的app
  • 工具之《activemq管理界面介绍》
  • 手把手教你用Python爬虫入门
  • excel文件损坏修复绝招_修复数据工具大盘点,让你快速掌握电脑数据恢复的秘密武器...
  • 13.【openCV_imread()函数详解】
  • 电子工程师-单片机的开发过程1
  • Qt学习12:常用Layout布局方式总结
  • 二进制文件、文本文件
  • linux tar命令解析(压缩解压)(tar指令)(十分之坑,千万不要在windows上解压带有软链接的压缩文件,会把软链接搞没了!软链接丢失、软链接失效)
  • 串联/并联谐振电路及其应用
  • 前端必知:29,改变鼠标手势及鼠标手势的应用
  • 计算机组成原理 01:计算机的发展历程