1、定义
可以直接从内存或者DMA等硬件接口取得图像数据,是个非常重要的绘图容器。
它的特性是:可以在主线程之外的线程中向屏幕绘图上。这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。在游戏开发中多用到SurfaceView,游戏中的背景、人物、动画等等尽量在画布canvas中画出。
2、实现
首先继承SurfaceView并实现SurfaceHolder.Callback接口
使用接口的原因:因为使用SurfaceView 有一个原则,所有的绘图工作必须得在Surface 被创建之后才能开始(Surface—表面,这个概念在 图形编程中常常被提到。基本上我们可以把它当作显存的一个映射,写入到Surface 的内容
可以被直接复制到显存从而显示出来,这使得显示速度会非常快),而在Surface 被销毁之前必须结束。所以Callback 中的surfaceCreated 和surfaceDestroyed 就成了绘图处理代码的边界。
需要重写的方法
(1)public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}
//在surface的大小发生改变时激发
(2)public void surfaceCreated(SurfaceHolder holder){}
//在创建时激发,一般在这里调用画图的线程。
(3)public void surfaceDestroyed(SurfaceHolder holder) {}
//销毁时激发,一般在这里将画图的线程停止、释放。
整个过程:继承SurfaceView并实现SurfaceHolder.Callback接口 ----> SurfaceView.getHolder()获得SurfaceHolder对象 ---->SurfaceHolder.addCallback(callback)添加回调函数---->SurfaceHolder.lockCanvas()获得Canvas对象并锁定画布----> Canvas绘画 ---->SurfaceHolder.unlockCanvasAndPost(Canvas canvas)结束锁定画图,并提交改变,将图形显示。
3、SurfaceHolder
这里用到了一个类SurfaceHolder,可以把它当成surface的控制器,用来操纵surface。处理它的Canvas上画的效果和动画,控制表面,大小,像素等。
几个需要注意的方法:
(1)、abstract void addCallback(SurfaceHolder.Callback callback);
// 给SurfaceView当前的持有者一个回调对象。
(2)、abstract Canvas lockCanvas();
// 锁定画布,一般在锁定后就可以通过其返回的画布对象Canvas,在其上面画图等操作了。
(3)、abstract Canvas lockCanvas(Rect dirty);
// 锁定画布的某个区域进行画图等..因为画完图后,会调用下面的unlockCanvasAndPost来改变显示内容。
// 相对部分内存要求比较高的游戏来说,可以不用重画dirty外的其它区域的像素,可以提高速度。
(4)、abstract void unlockCanvasAndPost(Canvas canvas);
// 结束锁定画图,并提交改变。
4、实例:
主布局:
1 <?xml version="1.0" encoding="utf-8"?>
2 <LinearLayout
3 xmlns:android="http:///apk/res/android"
4 xmlns:app="http:///apk/res-auto"
5 xmlns:tools="http:///tools"
6 android:orientation="vertical"
7 android:layout_width="match_parent"
8 android:layout_height="match_parent"
9 tools:context="net.bwie.surfaceview.MainActivity">
10
11 <Button
12 android:id="@+id/buffer_btn"
13 android:layout_width="wrap_content"
14 android:layout_height="wrap_content"
15 android:text="SurfaceVie使用缓冲刷新UI几面"/>
16
17 <Button
18 android:id="@+id/video_btn"
19 android:layout_width="wrap_content"
20 android:layout_height="wrap_content"
21 android:text="SurfaceView播放视频"/>
22
23 </LinearLayout>
主布局的Activity:
1 public class MainActivity extends AppCompatActivity implements View.OnClickListener {
2
3 protected Button mBufferBtn;
4 protected Button mVideoBtn;
5
6 @Override
7 protected void onCreate(Bundle savedInstanceState) {
8 super.onCreate(savedInstanceState);
9 super.setContentView(R.layout.activity_main);
10 initView();
11 }
12
13 @Override
14 public void onClick(View view) {
15 Intent intent = new Intent();
16 if (view.getId() == .buffer_btn) {
17 intent.setClass(this, BufferActivity.class);
18 } else if (view.getId() == .video_btn) {
19 intent.setClass(this, VideoActivity.class);
20 }
21 startActivity(intent);
22 }
23
24 private void initView() {
25 mBufferBtn = (Button) findViewById(.buffer_btn);
26 mBufferBtn.setOnClickListener(MainActivity.this);
27 mVideoBtn = (Button) findViewById(.video_btn);
28 mVideoBtn.setOnClickListener(MainActivity.this);
29 }
30 }
SurfaceVie使用缓冲刷新UI几面:
布局
1 <?xml version="1.0" encoding="utf-8"?>
2 <RelativeLayout
3 xmlns:android="http:///apk/res/android"
4 xmlns:app="http:///apk/res-auto"
5 xmlns:tools="http:///tools"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent"
8 tools:context="net.bwie.surfaceview.activity.BufferActivity">
9
10 <SurfaceView
11 android:id="@+id/surface_view"
12 android:layout_width="match_parent"
13 android:layout_height="match_parent"/>
14
15 </RelativeLayout>
Activity
1 // SurfaceView配合子线程,使用双缓冲刷新UI界面
2 // SurfaceHolder:用于管理缓冲区Surface的类(生命周期)
3 public class BufferActivity extends AppCompatActivity implements SurfaceHolder.Callback {
4
5 protected SurfaceView mSurfaceView;
6 private SurfaceHolder mHolder;
7
8 @Override
9 protected void onCreate(Bundle savedInstanceState) {
10 super.onCreate(savedInstanceState);
11 super.setContentView(R.layout.activity_buffer);
12 initView();
13 initSurfaceHolder();
14 }
15
16 // 初始化Surface的管理者
17 private void initSurfaceHolder() {
18 mHolder = mSurfaceView.getHolder();
19 // 添加管理生命周期的接口回调
20 mHolder.addCallback(this);
21 }
22
23 private void initView() {
24 mSurfaceView = (SurfaceView) findViewById(.surface_view);
25 }
26
27 // 缓冲区创建
28 @Override
29 public void surfaceCreated(SurfaceHolder holder) {
30 Log.d("1507", "surfaceCreated");
31 new DrawThread(this).start();
32 }
33
34 // 缓冲区内容改变(子线程渲染UI的过程)
35 @Override
36 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
37 Log.d("1507", "surfaceChanged");
38 }
39
40 // 缓冲区销毁
41 @Override
42 public void surfaceDestroyed(SurfaceHolder holder) {
43 Log.d("1507", "surfaceDestroyed");
44 }
45
46 // 绘制UI的子线程
47 private static class DrawThread extends Thread {
48
49 // private BufferActivity mActivity;
50
51 // 使用弱引用持有Activity的实例,
52 // 当Activity销毁时,当前线程会释放Activity,避免Activity无法释放导致的内存泄漏
53 private WeakReference<BufferActivity> mWeakReference;
54
55 public DrawThread(BufferActivity activity) {
56 // mActivity = activity;
57 mWeakReference = new WeakReference<BufferActivity>(activity);
58 }
59
60 @Override
61 public void run() {
62 super.run();
63
64 // 通过弱引用获取持有的Activity实例
65 BufferActivity activity = mWeakReference.get();
66
67 if (activity == null) {
68 return;
69 }
70
71 // 获取SurfaceView的盖度
72 int height = activity.mSurfaceView.getHeight();
73
74 // 创建画笔
75 Paint paint = new Paint();
76 paint.setColor(Color.GREEN);// 画笔颜色
77 paint.setStrokeWidth(10);// 画笔粗细。注意:Java中设置的尺寸单位都是px
78 paint.setStyle(Paint.Style.FILL_AND_STROKE);// 设置实心
79 paint.setAntiAlias(true);// 设置是否抗锯齿
80
81 Canvas canvas = null;
82 for (int i = 0; i < height; i += 5) {
83
84 // 获取Surface中的画布
85 canvas = activity.mHolder.lockCanvas();// 锁定画布
86
87 // 当SurfaceView随着Activity销毁时,缓冲区Surface也会随着销毁,无法获取缓冲区的画布
88 if (canvas == null) {
89 return;
90 }
91
92 canvas.drawColor(Color.RED);// 设置画布背景覆盖之前的残影
93 // 使用画笔在画布上绘制指定形状
94 canvas.drawCircle(100, i + 50, 50, paint);// 圆心x坐标,圆心y坐标,半径,画笔
95
96 // 缓冲区的画布绘制完毕,需要解锁并提交给窗口展示
97 activity.mHolder.unlockCanvasAndPost(canvas);
98
99
100 }
101
102
103 }
104 }
105
106 }
SurfaceView播放视频:
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:orientation="vertical"
xmlns:android="http:///apk/res/android"
xmlns:app="http:///apk/res-auto"
xmlns:tools="http:///tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="net.bwie.surfaceview.activity.VideoActivity">
<Button
android:id="@+id/play_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="播放"/>
<net.bwie.surfaceview.widget.MyVideoSurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
Activity
/**
* 1、获取播放源
* 2、准备SurfaceView
* 3、多媒体交给MediaPlayer处理
*/
public class VideoActivity extends AppCompatActivity implements View.OnClickListener {
protected MyVideoSurfaceView mSurfaceView;
protected Button mPlayBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_video);
initView();
}
// 运行、可见
@Override
protected void onStart() {
super.onStart();
}
// 可交互
@Override
protected void onResume() {
super.onResume();
}
private void play() {
String videoPath = Environment.getExternalStorageDirectory().getPath() +
"/VID_20171117_144736.3gp";// 外部存储根路径
mSurfaceView.playVideo(videoPath);
}
private void initView() {
mSurfaceView = (MyVideoSurfaceView) findViewById(.surface_view);
mPlayBtn = (Button) findViewById(.play_btn);
mPlayBtn.setOnClickListener(VideoActivity.this);
}
@Override
public void onClick(View view) {
if (view.getId() == .play_btn) {
play();
}
}
}
MyVideoSurfaceView类:
1 public class MyVideoSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
2
3 private SurfaceHolder mHolder;
4 private MediaPlayer mMediaPlayer;
5
6 public MyVideoSurfaceView(Context context, AttributeSet attrs) {
7 super(context, attrs);
8
9 init();
10 }
11
12 private void init() {
13 // 获取Surface换朝哪个区的持有者
14 mHolder = getHolder();
15 mHolder.addCallback(this);
16 }
17
18
19 // 设置播放源
20 public void playVideo(String path) {
21 if (mMediaPlayer == null) {
22 mMediaPlayer = new MediaPlayer();
23 }
24
25 try {
26 // 设置播放源
27 mMediaPlayer.setDataSource(path);
28 // 设置多媒体的显示部分:使用SurfaceHolder渲染画面
29 mMediaPlayer.setDisplay(mHolder);
30 mMediaPlayer.prepare();
31 mMediaPlayer.start();
32 } catch (IOException e) {
33 e.printStackTrace();
34 }
35
36 }
37
38 @Override
39 public void surfaceCreated(SurfaceHolder holder) {
40
41 }
42
43 @Override
44 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
45
46 }
47
48 @Override
49 public void surfaceDestroyed(SurfaceHolder holder) {
50 mMediaPlayer.release();
51 mMediaPlayer = null;
52 }
53
54 }