实战酷毙了的自定义View(一)

3,674 阅读6分钟

前言

想想音视频可能得先放一段落,只能间歇性来更新了,因为最近确实对这方面有点迷茫,交叉面淘系大佬给我列出的知识体系确实过于庞大,我现在还是得先学着前辈们先把Java层做好,再进阶也不迟。先把精力主战JavaFlutter了。不过之后项目可能尽可能会用Kotlin来做,毕竟这是阿里现在的内部可能主流语言了,毕竟再云栖大会上登场就是Kotlin而不是Java

Gtihub传送门

目录

  1. 实战酷毙了的自定义View(一)
  2. 实战酷毙了的自定义View(二)
  3. 实战酷毙了的自定义View(三)

Paint和Canvas的基础使用

在之前的文章Android自定义View,你摸的透透的了?之中,我们知道过了View的绘制流程、过度绘制啊之类的东西。这是基础,但是现在说的PaintCanvas也是一种非常重要的基础了,他们用通俗的话来说,就是笔和画布,而这么多的绘制哪一个任务不是通过他们来完成的呢?

之前我们讲过音视频开发,那视频的概念,其实我们应该也已经有所了解了,就是一帧帧的图片嘛,绘制的速度超过人眼,那么就能够演变成视频了。

上面给出一个贝塞尔曲线的绘制过程,当然这个我已经用代码实现了。有兴趣的读者可以直接的去源码中的PathView类中进行查看。

其实我就是模拟了100次而已,但是你就会感觉整个图片他在动,这其实也是动画实现的一种原理了。

这个贝塞尔曲线的模拟,其实可以分为几个部分,两个相同的部分就是贝塞尔曲线他一条线和一个点的模拟,还有就是中间点,也就是贝塞尔曲线的位置变换模拟了。

那在这里我们需要注意的地方就来了,如何获取当前点的位置,显然我们只会使用PaintCanvas是远远不够的,因为,我们的小点显然需要通过位置变换计算得来的。这里我们就要引入我们一个类Path了。

Path

Path,从字面意思我们就知道,这是一个路径的意思了。

先来观摩一下他的的函数有什么,和Canvas可能不一样的地方就是,从draw变到了add,但是这样的区别,就能让Canvas绘制更加复杂的图形了,因为draw的对象都是已经封装好的圆形、长方形啊等等,而通过add你就能绘制出各种奇形怪状的形状了,而贝塞尔曲线就是他们其中的一份子。

PathMeasure

什么是PathMeasure呢?其实你关注上方,我们讲过了Path,但是终究还是没讲到如何去进行一个位置的计算。而其实这个任务,就是交给我们的这个PathMeasure来交接完成的。

fun init(){
    pathMeasure = PathMeasure(path, false)
        pathMeasure?.length.let {
            pathLength = it
            mStep = it?.div(INVALIDATE_TIMES)
        }
}

override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    if (pathLength!! >= mDistance!!) {
        mDistance?.let { pathMeasure?.getPosTan(it, mPos, mTan) }
        mDistance = mStep?.let { mDistance?.plus(it) } 
        canvas?.drawPoint(mPos?.get(0)!!, mPos?.get(1)!!, paintPoint!!)
        invalidate()
    }
}

具体代码还是需要看我已经完成的PathView,这里做一个简单的介绍,其实这个PathMeasure通过Path来计算长度,然后在通过我们传入的Distance来返回当前的PostionTan值,会有读者问了Tan有什么用,其实一般比较多数的用法就是就是一个角度的调整,如果使用一个点来显示,确实没什么效果,但是如果是一个Bitmap来现实的,没有Tan值,那么我们的图片就不会取转头了。

然后通过获取的PostionTan值来重绘,我们就可以完成我们的贝塞尔曲线绘制了

刮刮卡效果实战

之前已经知道了我们的这个图片如何去进行绘制,那这个时候我们就继续深化了。

首先第一个问题,什么是刮刮卡?

小二上图!!!

读者: cool!!想知道想知道,这个咋实现呀。

小易: 我现在还不想告诉你,嘿嘿。

第二个问题,他有什么组成部分?

你可能会说,蒙版和底层图片呗。但是这个答案是,也不是。为什么呢?如果只有蒙版和底层图片,其实这个不能够完全实现的,你可能只能实现到下面的步骤。

一个只能看,不能刮的刮刮卡,哈哈哈哈哈哈哈哈!!!

既然我们已经说了,这两个不够,那肯定还有其他组成成分是需要我们去进行考虑的了。其实他就是一支笔,这个刮的部分其实他是通过一支笔来完成的。

这里我们需要补充几个知识点了。

离屏绘制

通过写法如下:

override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    val layerId = canvas?.saveLayer(
            null, null
        )
    canvas?.let { canvasIt ->
        paint?.let {paintIt->
            rectBitmap?.let { canvas.drawBitmap(it,0f,0f, paintIt) }
            paintIt.xfermode = xfermode
            circleBitmap?.let { canvas.drawBitmap(it, 50f, 50f,paintIt) }
            paintIt.xfermode = null
        }
    }
    layerId?.let { canvas.restoreToCount(it) }
}

通过一个短时的离屏缓冲,来完成两个图层的复合来实现。而这个复合就是通过一个layerId来完成判定谁和谁是一家人。

Xfermode

这里又来一个知识点了!!!复合是怎么实现的???

下面给出一张图片来解释,这是Android内置的Xfermode,也就是复合模式是如何的,理解图片的意思即可,也不用专门去学怎么算的,归根结底,就是对RGB某一个色块的突出或者隐藏。

好了,知道了这几个知识点,我们回归正题,刮刮卡怎么实现的问题?我们知道了复合,知道了离屏绘制,怎么用呢?

其实最后要讨论的就是蒙版是什么?底层图片做什么?

底层图片自然是给我们看的,我们总不能手画一下,然后被我们抹掉吧,那这个图层显然是不能被我们的覆盖掉的。而蒙版呢,我们需要通过离屏绘制,把图片和我们走过的路径进行重叠消除,来完成看到图片的效果。那这个效果应该是那种模式来完成呢?我们看看上面给出的图片,那个最合适。

刮的过程就是我们的Dst和蒙版就是我们的Src,这个是应该就是XOR这个模式是最符合我们的要求的,我们绘制过的和Src重叠的地方全部消去了。

但是看过了源码的读者估计会问一个问题,为什么我要创建一个DBitmap来完成这个任务?其实不实现也可以的,但是你会出现下图的情况了,刮刮卡下方的图层一片乌漆麻黑,其实这个DBitmap起到的是一个限制的作用了,给了Paint的一个作用范围,那么实现的就更加舒服了,当然如果你限制了View在整合布局中的大小,正好占满也是可以解决这个问题的,但是并不推荐。

最后就是一个简单的也比较重要的点了,这是Android的事件分发机制都会问到的一个点,我们应该重写什么方法,来完成绘制路径的添加。建议还是在OnTouch中完成,因为OnTouch的响应其实是先于OnTouchEvent的。