简介
官方API文档介绍:SurfaceView是View的子类,它内嵌了一个专门用于绘制的Surface,你可以控制这个Surface的格式和尺寸,Surfaceview控制这个Surface的绘制位置。
surface是纵深排序(Z-ordered)的,这表明它总在自己所在窗口的后面。SurfaceView提供了一个可见区域,只有在这个可见区域内的surface内容才可见。surface的排版显示受到视图层级关系的影响,它的兄弟视图结点会在顶端显示。这意味者 surface的内容会被它的兄弟视图遮挡,这一特性可以用来放置遮盖物(overlays)(例如,文本和按钮等控件)。注意,如果surface上面有透明控件,那么每次surface变化都会引起框架重新计算它和顶层控件的透明效果,这会影响性能。
你可以通过SurfaceHolder接口访问这个surface,getHolder()方法可以得到这个接口。
SurfaceView变得可见时,surface被创建;SurfaceView隐藏前,surface被销毁。这样能节省资源。如果你要查看 surface被创建和销毁的时机,可以重载surfaceCreated(SurfaceHolder)和 surfaceDestroyed(SurfaceHolder)。
SurfaceView的核心在于提供了两个线程:UI线程和渲染线程。这里应注意:
- 所有SurfaceView和SurfaceHolder.Callback的方法都应该在UI线程里调用,一般来说就是应用程序主线程。渲染线程所要访问的各种变量应该作同步处理。
- 由于surface可能被销毁,它只在SurfaceHolder.Callback#surfaceCreated()和 SurfaceHolder.Callback#surfaceDestroyed()之间有效,所以要确保渲染线程访问的是合法有效的surface。
注意:从平台版本Build.VERSION_CODES.N开始,SurfaceView的窗口位置与其他View渲染同步更新。这意味着在屏幕上平移和缩放SurfaceView不会导致渲染失真。当它的窗口异步渲染时,可能会在之前的安卓版本中发生渲染失真。
双缓冲技术
为了防止动画闪烁而实现的一种多线程应用。主要原理:一个画面显示过程中,程序又实时在改变它,前画面还没有显示完,程序又请求重新绘制,这样屏幕就会不停闪烁。为了避免闪烁,可以使用双缓冲技术,将要处理的图片都在内存中处理好之后,再将其显示到屏幕上。这样显示出来的总是完整的图像,不会出现闪烁现象。
在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上,这样能大大加快绘图的速度。
SurfaceView默认使用双缓冲技术,它支持在子线程中绘制图像,这样就不会阻塞主线程了,所以它适合于视频或图像相关的开发。
参考:android双缓冲绘图技术分析
SurfaceView的使用
下面我将用代码形式,展示如何使用SurfaceView。
一、绘制图形
class ImageSurfaceView(context: Context?) : SurfaceView(context), SurfaceHolder.Callback {
var surfaceHolder: SurfaceHolder? = null
var drawThread: DrawThread? = null
init {
surfaceHolder = holder
surfaceHolder?.addCallback(this)//监听surface的状态
if (surfaceHolder != null) {
drawThread = DrawThread(surfaceHolder!!)
}
}
override fun surfaceCreated(holder: SurfaceHolder?) {
//surface创建的时候调用,一般在该方法中启动绘图的线程
Log.i("Test", "surface创建")
drawThread?.isRunning = true
drawThread?.start()
}
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
//surface尺寸发生改变的时候调用,如横竖屏切换,或者初次渲染在屏幕上时
Log.i("Test", "surface更新")
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
//surface被销毁的时候调用,一般在该方法中停止绘图线程
Log.i("Test", "surface销毁")
drawThread?.isRunning = false
}
/**
* 异步线程,不停地绘制文字
*/
inner class DrawThread(val sHolder: SurfaceHolder) : Thread() {
var isRunning = true
var count = 0
override fun run() {
while (isRunning) {
var c: Canvas? = null
try {
synchronized(sHolder) {
c = sHolder.lockCanvas()//获取画布,以执行绘制
c?.drawColor(Color.WHITE)//画布背景白色
val paint = Paint()
paint.color = Color.RED
paint.textSize = 60f
c?.drawText("${count++}秒", 200f, 500f, paint)//绘制时
sleep(1000)
}
}catch (e:Exception){
}finally {
if (c != null) {
sHolder.unlockCanvasAndPost(c)//释放画布并显示所绘制的内容到屏幕上
}
}
}
}
}
}
二、播放视频
使用MediaPlayer+SurfaceView播放视频,安卓还有个VideoView是继承SurfaceView专门用于播放视频的,那个可以自行百度。
class SurfaceActivity : AppCompatActivity(), SurfaceHolder.Callback {
//媒体播放控制器
private var mediaPlayer: MediaPlayer? = null
//视频路径
private var videoPath: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_surface)
////把输送给surfaceView的视频画面,直接显示到屏幕上,不要维持它自身的缓冲区
sv_video.holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
sv_video.holder.setKeepScreenOn(true)
sv_video.holder.addCallback(this)
initMedia()
btn_play.setOnClickListener {
if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
pause()
} else {
start()
}
}
}
/**
* 初始化媒体播放
*/
private fun initMedia() {
videoPath = "/storage/emulated/0/DCIM/Camera/VID_20200311_082801.mp4"
mediaPlayer = MediaPlayer()
mediaPlayer?.setDataSource(videoPath)
mediaPlayer?.setOnPreparedListener {
start()//缓冲完,播放
}
mediaPlayer?.setOnCompletionListener {
Toast.makeText(this,"播放完毕",Toast.LENGTH_SHORT).show()
btn_play.text = "重新播放"
}
}
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
mediaPlayer!!.stop()
}
}
override fun surfaceCreated(holder: SurfaceHolder?) {
mediaPlayer?.setDisplay(holder)//设置播放的容器,将MediaPlayer和SurfaceView关联起来
mediaPlayer?.prepareAsync()//缓冲
}
private fun start(){
btn_play.text = "暂停"
mediaPlayer?.start()
}
private fun pause(){
btn_play.text = "播放"
mediaPlayer?.pause()
}
override fun onDestroy() {
super.onDestroy()
mediaPlayer?.release()
mediaPlayer = null
}
}