簡介
官方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
}
}