1. 前语

写这篇文章也是一个机缘巧合。曾经在学习自定义View的时分有了解过PorterDuffXfermode,可是也就只知道个大概,并没有在实战中运用到。 直到前段时间有个需求是做个指引流程的显现,我才想到PorterDuffXfermode能够完成这个功用,结果抱着幸运的心理试了一下,真的就能完成。
所以根底知识仍是比较重要的,虽然平时用不上,可是当你了解它是做什么的,一到可能用得上的场景时分,你就能联想出运用这项技能来完成。

2. 完成作用

先来看看完成作用,我要做一个指引流程的作用,要除了指引处是高亮,其他地方都置灰,以此来突出重点地方。作用大概是这样

Android 指引流程使用PorterDuffXfermode实现

平时这种作用还有一些图片和文字注释还有箭头,我这儿为了方便演示,写个Demo就不带这些东西了。图片文字箭头这些都很简略去弄,主要是很多人可能不清楚这个高亮的作用怎样完成。

3. 完成思路

这种高亮的作用也能够有很多种思路,有很多种方案去完成。比如说能够先加一个蒙层,然后再增加一个巨细和方位如出一辙的view到蒙层上面,也能完成这样的作用。

可是当我看到这个需求的时分,我榜首个想法是怎样让蒙层漏一个洞。这时分我就想到了PorterDuffXfermode,对它最底子的了解便是,能完成两个制作区域各种叠加的作用。

能够看看官网对PorterDuff.Mode的介绍 developer.android.com/reference/a…

Android 指引流程使用PorterDuffXfermode实现

Android 指引流程使用PorterDuffXfermode实现

而我这儿要完成的作用是两个区域叠加,然后移除两块制作区域的重叠区域,正好有个XOR是这样的作用。

Android 指引流程使用PorterDuffXfermode实现

4. 完成

大致了解了运用的技能和原理,就来试试看具体是怎样去完成。

首要我把整个指引流程作用的制作当成一个View,要自定义一个view,巨细是全屏,制作蒙层,并经过PorterDuffXfermode完成高亮区域(也便是让蒙层漏一个洞)

其实PorterDuffXfermode这个技能,能够把两个制作的区域分为原图和方针图

原图的制作直接这样就行,简略方便。

canvas?.drawColor(Color.parseColor("#80000000"))

方针图我们需求对画笔进行一下初始化

init {
    // 让vg完成onDraw
    setWillNotDraw(false)
    // 方针区域
    targetPaint = Paint()
    targetPaint?.color = Color.parseColor("#ffffff")
    targetPaint?.isAntiAlias = true
    // 设置Mode为XOR
    targetPaint?.xfermode = PorterDuffXfermode(PorterDuff.Mode.XOR)
    // 封闭硬件加速
    setLayerType(View.LAYER_TYPE_SOFTWARE, null)
}

注意这儿需求setLayerType(View.LAYER_TYPE_SOFTWARE, null)封闭硬件加速,因为PorterDuffXfermode要封闭硬件加速才有作用。这个也是运用PorterDuffXfermode时的一个比较经典的问题。

然后我们需求制作方针图,可是这个方针图的尺寸我们需求传进来。

var rectf: RectF? = null
fun setContentLocation(x: Int, y: Int, w: Int, h: Int) {
    // 区域便是高亮的方针控件的区域加4dp
    rectf = RectF(
        x.toFloat() - dip2px(4f),
        y.toFloat() - dip2px(4f),
        (x + w).toFloat() + dip2px(4f),
        (y + h).toFloat() + dip2px(4f)
    )
}

然后制作

targetPaint?.let {
    // 加个弧度,纯长方形不好看
    canvas?.drawRoundRect(
        rectf!!,
        dip2px(18f).toFloat(),
        dip2px(18f).toFloat(),
        it
    )
}

具体的代码

class GuideView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
    private var targetPaint: Paint? = null
    init {
        // 让vg完成onDraw
        setWillNotDraw(false)
        // 方针区域
        targetPaint = Paint()
        targetPaint?.color = Color.parseColor("#ffffff")
        targetPaint?.isAntiAlias = true
        // 设置Mode为XOR
        targetPaint?.xfermode = PorterDuffXfermode(PorterDuff.Mode.XOR)
        // 封闭硬件加速
        setLayerType(View.LAYER_TYPE_SOFTWARE, null)
    }
    var rectf: RectF? = null
    fun setContentLocation(x: Int, y: Int, w: Int, h: Int) {
        rectf = RectF(
            x.toFloat() - dip2px(4f),
            y.toFloat() - dip2px(4f),
            (x + w).toFloat() + dip2px(4f),
            (y + h).toFloat() + dip2px(4f)
        )
    }
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.drawColor(Color.parseColor("#80000000"))
        targetPaint?.let {
            canvas?.drawRoundRect(
                rectf!!,
                dip2px(18f).toFloat(),
                dip2px(18f).toFloat(),
                it
            )
        }
    }
    fun dip2px(dpValue: Float): Int {
        val scale = Resources.getSystem().displayMetrics.density
        return (dpValue * scale + 0.5f).toInt()
    }
}

然后Demo里面有一个方针控件,我这儿就用TextView,让高亮区域作用在这个控件上

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fl"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="22sp"
        android:textColor="#000000"
        android:text="引导此处"
        android:textStyle="bold"
        android:layout_marginTop="60dp"
        android:layout_marginStart="60dp"
        />
</FrameLayout>
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
//        val wlp = window.attributes
//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
//            wlp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
//        }
//        window.attributes = wlp
//        window.decorView.systemUiVisibility =
//            View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        setContentView(R.layout.main)
        val tv: TextView = findViewById(R.id.tv)
        val fl: FrameLayout = findViewById(R.id.fl)
        tv.post {
            // 获取view方位
            val location = IntArray(2)
            tv.getLocationInWindow(location)
            val x = location[0]
            val y = location[1]
            // 创立引导view
            val guideView = GuideView(this)
            guideView.layoutParams = FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
            guideView.setContentLocation(x, y, tv.width, tv.height)
            fl.addView(guideView)
        }
    }
}

看得出很简略,只需求创立指引控件,然后把需求指向的方针控件的方位传给它就行。能够看看最终的作用。

Android 指引流程使用PorterDuffXfermode实现

有人说,嗯?不对啊,你这个高亮区域都没对齐,都歪了。这是我成心的,认真想想歪的尺寸是多少?认真想想我注释的代码,如果真实不知道为什么,不要紧,能够看看我这篇文章【狗头】 /post/720133…

5. 总结

写这篇文章,对外而言,能够介绍一下指引流程空间的完成方式和PorterDuffXfermode的根底用法。对我而言因为是一次比较有意思的体会,所以做个记载。

拿到这个需求的时分我并没有去查怎样完成,而是榜首反应想到应该能够经过PorterDuffXfermode给蒙层挖个洞的方式。果然根底很重要,哪怕现在成熟的框架很多,但他们的底层完成也是根据某些技能的合理运用。