1. 前语
写这篇文章也是一个机缘巧合。曾经在学习自定义View的时分有了解过PorterDuffXfermode,可是也就只知道个大概,并没有在实战中运用到。
直到前段时间有个需求是做个指引流程的显现,我才想到PorterDuffXfermode能够完成这个功用,结果抱着幸运的心理试了一下,真的就能完成。
所以根底知识仍是比较重要的,虽然平时用不上,可是当你了解它是做什么的,一到可能用得上的场景时分,你就能联想出运用这项技能来完成。
2. 完成作用
先来看看完成作用,我要做一个指引流程的作用,要除了指引处是高亮,其他地方都置灰,以此来突出重点地方。作用大概是这样
平时这种作用还有一些图片和文字注释还有箭头,我这儿为了方便演示,写个Demo就不带这些东西了。图片文字箭头这些都很简略去弄,主要是很多人可能不清楚这个高亮的作用怎样完成。
3. 完成思路
这种高亮的作用也能够有很多种思路,有很多种方案去完成。比如说能够先加一个蒙层,然后再增加一个巨细和方位如出一辙的view到蒙层上面,也能完成这样的作用。
可是当我看到这个需求的时分,我榜首个想法是怎样让蒙层漏一个洞。这时分我就想到了PorterDuffXfermode,对它最底子的了解便是,能完成两个制作区域各种叠加的作用。
能够看看官网对PorterDuff.Mode的介绍 developer.android.com/reference/a…
而我这儿要完成的作用是两个区域叠加,然后移除两块制作区域的重叠区域,正好有个XOR是这样的作用。
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)
}
}
}
看得出很简略,只需求创立指引控件,然后把需求指向的方针控件的方位传给它就行。能够看看最终的作用。
有人说,嗯?不对啊,你这个高亮区域都没对齐,都歪了。这是我成心的,认真想想歪的尺寸是多少?认真想想我注释的代码,如果真实不知道为什么,不要紧,能够看看我这篇文章【狗头】 /post/720133…
5. 总结
写这篇文章,对外而言,能够介绍一下指引流程空间的完成方式和PorterDuffXfermode的根底用法。对我而言因为是一次比较有意思的体会,所以做个记载。
拿到这个需求的时分我并没有去查怎样完成,而是榜首反应想到应该能够经过PorterDuffXfermode给蒙层挖个洞的方式。果然根底很重要,哪怕现在成熟的框架很多,但他们的底层完成也是根据某些技能的合理运用。