欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SurfaceView的使用

程序员文章站 2022-03-30 17:09:24
...

引子

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的使用

  1. 创建一个自定义的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
 * 博客:  http://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
                 }

             }


         }
     }
   }

如上,完成一个被动绘制和开放两个主动绘制的方法。

  1. 在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>

  1. 在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的使用,欢迎访问我的个人博客,关注微信公众号 “音视频爱好者” 。

相关标签: surfaceView android