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://schemas.android.com/apk/res/android" 4 xmlns:app="http://schemas.android.com/apk/res-auto" 5 xmlns:tools="http://schemas.android.com/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() == R.id.buffer_btn) { 17 intent.setClass(this, BufferActivity.class); 18 } else if (view.getId() == R.id.video_btn) { 19 intent.setClass(this, VideoActivity.class); 20 } 21 startActivity(intent); 22 } 23 24 private void initView() { 25 mBufferBtn = (Button) findViewById(R.id.buffer_btn); 26 mBufferBtn.setOnClickListener(MainActivity.this); 27 mVideoBtn = (Button) findViewById(R.id.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://schemas.android.com/apk/res/android" 4 xmlns:app="http://schemas.android.com/apk/res-auto" 5 xmlns:tools="http://schemas.android.com/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(R.id.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://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/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(R.id.surface_view); mPlayBtn = (Button) findViewById(R.id.play_btn); mPlayBtn.setOnClickListener(VideoActivity.this); } @Override public void onClick(View view) { if (view.getId() == R.id.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 }
别忘了加权限:
1 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>