萌萌の初音
萌萌の初音
发布于 2022-05-13 / 814 阅读
0

SurfaceView的简单实现

SurfaceView介绍

在Android中更新UI必须保证操作都在主线程中,如果画面经常刷新变动就会导致主线程阻塞,影响用户体验或更严重的ANR,这时就需要使用SurfaceView进行处理。
SurfaceView可以另起一个线程单独进行画面更新的,通过对cavans进行绘制来实现画面显示,在重复的动画中可以对cavans绘制的bitmap用LruCache进行缓存来优化性能。

简单的使用(kotlin实现)

1.需要继承SurfaceView以及对SurfaceHolder.Callback接口方法进行重写

class ResignView : SurfaceView, SurfaceHolder.Callback {
    constructor(context: Context?) : super(context) {holder.addCallback(this)}
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {holder.addCallback(this)}
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {holder.addCallback(this)}

    override fun surfaceCreated(p0: SurfaceHolder) {}

    override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {}

    override fun surfaceDestroyed(p0: SurfaceHolder) {}
}

holder.addCallback(this):绑定接口实现。
surfaceCreated:SurfaceView刚创建时的回调,这个方法里我们可以对Surface进行一些设定、以及动画方法的开始。
surfaceChanged:在画面有变动时的回调,比如屏幕旋转,小窗口模式等,这时候我们就需要对绘制动画的坐标、比例等进行修改。
surfaceDestroyed:在画面销毁时的操作,需要手动释放的资源等都在这里进行处理,防止内存泄漏。
ps:如果需要透明无遮挡的画布,需要在surfaceCreated回调中添加以下方法:

        this.setZOrderOnTop(true)
        holder?.setFormat(PixelFormat.TRANSPARENT)

2.对cavans进行绘制

在surfaceCreated之后的调用中可以起一个线程,通过holder.lockCanvas()获取canvas,进行相应的绘制,绘制完成后再通过holder.unlockCanvasAndPost(canvas)进行显示

    private var isRun = false
    private val paint = Paint()
    private val mPaint = Paint()
    private val list = mutableListOf<MutableMap<String, Float>>()
    private fun pushMax() {
        isRun = true
        paint.style = Paint.Style.FILL
        paint.color = Color.RED
        paint.isAntiAlias = true

        mPaint.color = Color.WHITE
        mPaint.textSize = dpToPx(16)
        Thread {
            paint.color = Color.argb(0xff, 0x01, 0xB8, 0xD1)
            while (isRun) {
                val map = mutableMapOf<String, Float>()
                holder?.lockCanvas()?.let { canvas ->
                    canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)

                    map["pointX"] = Random.nextInt(0, canvas.width/Random.nextInt(1,3))*1f
                    map["pointY"] = Random.nextInt(0, canvas.height/Random.nextInt(1,3))*1f

                    list.add(map)

                    list.forEach {
                        pointX = it["pointX"] ?:0f
                        pointY = it["pointY"] ?:0f

                        canvas.drawRoundRect(
                            pointX,
                            pointY,
                            pointX+dpToPx(88),
                            pointY+dpToPx(44),
                            8f, 8f, paint)

                        canvas.drawText(
                            context.getString(R.string.agree),
                            pointX+dpToPx(28),
                            pointY+dpToPx(28), mPaint)

                    }

                    holder?.unlockCanvasAndPost(canvas)
                }
                Thread.sleep(500)
            }
        }.start()
    }

通过while 进行循环来实现动画的绘制;canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)实现画布清空,背景色透明的实现;isRun 对循环进行处理(简单的实现,最好用线程池来管理),list、map对绘制的坐标进行缓存(之后的点击事件会用到)。

3.点击事件的检测

需要重写onTouchEvent方法,并在MotionEvent.ACTION_DOWN事件中进行检测。需要在MotionEvent.ACTION_UP事件中对performClick()方法进行执行。

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when (event?.action) {
            MotionEvent.ACTION_UP -> performClick()
            MotionEvent.ACTION_DOWN -> {
                list.forEach {
                        val pointX = it["pointX"]?:0f
                        val pointY = it["pointY"]?:0f
                        val rect = Rect(pointX.toInt(), pointY.toInt(), pointX.toInt() + dpToPx(88).toInt(), pointY.toInt() + dpToPx(44).toInt())
                        when {
                            rect.contains(event.x.toInt(), event.y.toInt()) -> {
                                Toast.makeText(context, context.getString(R.string.thank), Toast.LENGTH_SHORT).show()
                                (context as Activity).finish()
                            }
                            else -> {}
                        }
                    }
            }
            else -> {}
        }
        return super.onTouchEvent(event)
    }

我们绘制的是矩形,将缓存的坐标list遍历,通过Rect类进行判断即可确定是否在绘制范围内。

4.使用方法

可通过xml布局文件,或者通过addview方法加入。

demo地址:

私有库/github

预览效果:

Screenrecorder_2022_05_16.gif