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 创建时调用
@Override
public void surfaceCreated(SurfaceHolder holder) {

}

// SurfaceView 改变时调用
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

// SurfaceView 销毁时调用
@Override
public void surfaceDestroyed(SurfaceHolder holder) {

}

@Override
public 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();
//注册 SurfaceHolder
mSurfaceHolder.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();
//注册 SurfaceHolder
mSurfaceHolder.addCallback(this);
// 设置可以获取焦点
setFocusable(true);
// 设置触摸可以获取焦点
setFocusableInTouchMode(true);
// 保持屏幕常亮
this.setKeepScreenOn(true);
}

// SurfaceView 创建时调用
@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsDrawing = true;
// 通过线程池来执行
Executors.newCachedThreadPool().execute(this);
}

// SurfaceView 改变时调用
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

// SurfaceView 销毁时调用
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsDrawing = false;
}

@Override
public 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();
//注册 SurfaceHolder
mSurfaceHolder.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 创建时调用
@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsDrawing = true;
// 通过线程池来执行
Executors.newCachedThreadPool().execute(this);
}

// SurfaceView 改变时调用
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}

// SurfaceView 销毁时调用
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsDrawing = false;
}

@Override
public 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);
}
}
}

@Override
public 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;
}
}

       运行效果如下图:

SurfaceView 的基本使用

     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;

@Override
public void run() {
drawing();
synchronized (MySurfaceView.this) {
while (mIsDrawing) {
if (mIsWait) {
try {
MySurfaceView.this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
drawing();
}
}
}

...

@Override
public 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表示需要等待。

原创:https://www.panoramacn.com
源码网提供WordPress源码,帝国CMS源码discuz源码,微信小程序,小说源码,杰奇源码,thinkphp源码,ecshop模板源码,微擎模板源码,dede源码,织梦源码等。

专业搭建小说网站,小说程序,杰奇系列,微信小说系列,app系列小说

SurfaceView 的基本使用

免责声明,若由于商用引起版权纠纷,一切责任均由使用者承担。

您必须遵守我们的协议,如果您下载了该资源行为将被视为对《免责声明》全部内容的认可-> 联系客服 投诉资源
www.panoramacn.com资源全部来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。 敬请谅解! 侵权删帖/违法举报/投稿等事物联系邮箱:2640602276@qq.com
未经允许不得转载:书荒源码源码网每日更新网站源码模板! » SurfaceView 的基本使用
关注我们小说电影免费看
关注我们,获取更多的全网素材资源,有趣有料!
120000+人已关注
分享到:
赞(0) 打赏

评论抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

您的打赏就是我分享的动力!

支付宝扫一扫打赏

微信扫一扫打赏