引子
SurfaceView是Android中较为特殊的视图,它继承自View,但与View不同的是它用于单独的绘画图层,平行与当前Activity的独立绘画图层,且它的图层在层次排列上在Activity图层的下面,因此需要在Activity图层上限时一块透明的区域,用于显示SurfaceView图层,所以其本质是SurfaceView本身任然为Activity其上的一个透明子View,只是SurfaceView中有一个Surface对象用于绘制一个平行与当前Activity且处于surfaceView之下的图层。Surface相较于Acitivity图层的不同在于,Activity图层之上的每一个View的绘制都会导致Activity的重绘,View通过刷新来重绘视图,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新的时间间隔一般为16ms,在一些需要频繁刷新的界面,如果刷新执行很多逻辑绘制操作,就会导致刷新使用时间超过了16ms,就会导致丢帧或者卡顿,比如你更新画面的时间过长,那么你的主UI线程会被你的绘制函数阻塞,那么将无法响应按键,触屏等消息,会造成ANR问题。而与View不同SurfaceView的绘制方式效率非常高,因为SurfaceView的窗口刷新的时候不需要重绘应用程序的窗口,SurfaceView拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面,由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行行绘制,由于不占用主线程资源,使得它可以实现大多复杂而高效的界面绘制,如视频播放
VideoView 和OpenGl es的 GLSurfaceView 。
SurfaceView的特点
- SurfaceView属于被动绘制
当SurfaceView为可见状态下调用surfaceCreated(),创建其内的Surface图层,并可以进行绘制的初始化操作。当surfaceView为隐藏状态(不可见)当前surface会被销毁。属于被动调用,但并不是说不能主动绘制,一般的,在SurfaceHolder.Callback的surfaceCreated与surfaceDestroyed之间都是可以正常进行绘制的。 - SurfaceView 可在任意线程进行绘制
与一般的View必须在主线程中绘制不同,SurfaceView由于其特有的单独图层的特性让其可以在任意线程中绘制,可以减少对主线程资源的持有和完成大多比较平凡耗时的绘制工作。 - SurfaceVie使用双缓冲机制
双缓冲即在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上,这样能大大加快绘图的速度。在图形图象处理编程过程中,双缓冲是一种基本的技术。在Android中当要绘制的数据量比较大,绘图时间比较长时,重复绘图会出现闪烁现象,引起闪烁现象的主要原因是视觉反差比较大,使用双缓冲技术可以有效解决这个问题。
SurfaceView的使用
- 创建一个自定义的SurfaceView,并实现其内的SurfaceHolder.Callback,如下:
package cn.enjoytoday.shortvideo.test.ui.customsurface
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.SurfaceHolder
import android.view.SurfaceHolder.SURFACE_TYPE_NORMAL
import android.view.SurfaceView
import java.lang.Exception
/**
* 作者: hfcai
* 时间: 19-4-22
* 博客: https://www.enjoytoday.cn
* 描述: 自定义SurfaceView
*/
class CustomerSurfaceView(context: Context, attributes: AttributeSet?, defStyleAttr:Int)
:SurfaceView(context,attributes,defStyleAttr), SurfaceHolder.Callback {
var mIsDrawing = false
var x =1
var y = 0;
private var mPath:Path?=null
private var mPaint: Paint?=null
var mCanvas:Canvas?=null
/**
* 图层改变,当Surface的状态(大小和格式)发生变化的时候会调用该函数,在surfaceCreated调用后该函数至少会被调用一次
*/
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
}
/**
* 图层销毁,surfaceView隐藏前surface会被销毁
*/
override fun surfaceDestroyed(holder: SurfaceHolder?) {
mIsDrawing =false
}
/**
* 图层创建,surfaceView可见时surface会被创建
* 创建后可以开始绘制surface界面
*
*/
override fun surfaceCreated(holder: SurfaceHolder?) {
holder?.let {
mIsDrawing =true
SinThread().start()
// mCanvas = it.lockCanvas() //lockCanvas锁定整个画布,不缓存绘制,同步线程锁
// mCanvas =it.lockCanvas(Rect(0,0,200,200)) //锁定指定位置画布,指定范围外的画布不重新绘制(缓存)
// //解除线程锁,并提交绘制显示图像
// it.unlockCanvasAndPost(mCanvas)
}
}
/**
* 构造方法
*/
constructor(context: Context, attributes: AttributeSet?):this(context,attributes,-1)
constructor(context: Context):this(context,null)
init {
//初始化操作
holder.addCallback(this)
isFocusable = true
isFocusableInTouchMode = true
keepScreenOn = true
}
/**
* 刷新绘制
*/
fun refreshSin(){
SinThread().start()
}
fun cos(){
CosThread().start()
}
/**
* 正弦函数
*/
inner class SinThread :Thread(){
override fun run() {
x =1
y=0
mPaint = Paint()
mPaint?.strokeWidth=12f
mPaint?.color = Color.BLUE
mPath = Path()
while (mIsDrawing) {
try {
mCanvas = holder.lockCanvas()
mCanvas?.drawColor(Color.WHITE)
mCanvas?.drawPath(mPath, mPaint)
} catch (e: Exception) {
e.printStackTrace()
} finally {
if (mCanvas != null) {
holder?.unlockCanvasAndPost(mCanvas)
}
}
x+=1
if (x<=width) {
y = (100*Math.sin(x*2*Math.PI/180)+400).toInt()
mPath?.lineTo(x.toFloat(),y.toFloat())
}else{
break
}
}
}
}
/**
* 余弦函数
*/
inner class CosThread :Thread(){
override fun run() {
x =1
y=0
mPaint = Paint()
mPaint?.strokeWidth=12f
mPaint?.color = Color.BLUE
mPath = Path()
while (mIsDrawing) {
try {
mCanvas = holder.lockCanvas()
mCanvas?.drawColor(Color.WHITE)
mCanvas?.drawPath(mPath, mPaint)
} catch (e: Exception) {
e.printStackTrace()
} finally {
if (mCanvas != null) {
holder?.unlockCanvasAndPost(mCanvas)
}
}
x+=1
if (x<=width) {
y = (100*Math.cos(x*2*Math.PI/180)+400).toInt()
mPath?.lineTo(x.toFloat(), y.toFloat())
}else{
break
}
}
}
}
}
如上,完成一个被动绘制和开放两个主动绘制的方法。
- 在xml中使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<cn.enjoytoday.shortvideo.test.ui.customsurface.CustomerSurfaceView
android:id="@+id/customerSurfaceView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="300dp"/>
<LinearLayout
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/customerSurfaceView"
android:layout_marginTop="20dp"
android:padding="10dp"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/beginDraw"
android:text="开始绘制"
android:onClick="onClick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/cosDraw"
android:text="绘制cos"
android:onClick="onClick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
- 在activity控制
class CustomSurfaceActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_custom_surface)
}
/**
* 点击事件监听
*/
fun onClick(view: View){
when(view.id){
R.id.beginDraw -> customerSurfaceView.refreshSin()
R.id.cosDraw -> customerSurfaceView.cos()
}
}
}
测试代码可见:[SurfaceView的使用](https://github.com/amikoj/shortVideo/tree/mvvm-
liveData/test)