Andorid下的控件画布-SurfaceView

引子

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

展开阅读全文
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值