前言
不能做铺路人,做个引路人也好~
此文着重讲解张鸿洋大神的HighLightView的使用细节,大佬的库用过不少,或多或少的与产品业务有所不同,或者设计瑕疵的地方,无可厚非,此时作为优秀的‘百度开发者’,我们应该有阅读甚至修改源码的能理,这样才能举一反三,融会贯通。
- 结合kotlin的函数式编程,言简意赅,闭包且简洁,一撸到底,墙裂推荐一波~
var mHightLight: HighLight? = null
var mOneModeLightView: HighLight? = null
var mTwoModeLightView: HighLight? = null
/*
* 新手指引:
* 注释:
* @author maqi
* created at 2021/11/30 10:13
*/
private fun newUserGuide2SOr2T() {
val offest = DensityUtils.dp2pxFloat(requireContext(), 2f)
val customShape = object : BaseLightShape(0F, 0F, 5f) {
override fun resetRectF4Shape(viewPosInfoRectF: RectF?, dx: Float, dy: Float) {
viewPosInfoRectF?.offset(offest, offest)
}
override fun drawShape(bitmap: Bitmap, viewPosInfo: HighLight.ViewPosInfo) {
val canvas = Canvas(bitmap)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.isDither = true
paint.isAntiAlias = true
paint.color = Color.argb(100, 255, 255, 255)
if (blurRadius > 0) {
paint.maskFilter = BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.SOLID)
}
val rectF = viewPosInfo.rectF
rectF.left -= offest
rectF.top -= offest
canvas.drawRect(rectF, paint)
val bgBitmap = BitmapUtils.drawableToBitmap(requireContext(),
R.drawable.shape_radius_board_2dp,
rectF.width().toInt(),
rectF.height().toInt()
)
canvas.drawBitmap(bgBitmap!!, rectF.left, rectF.top, paint)
// val bgBitmap = ImageFileCache.getBitmap(
// requireContext(),
// R.drawable.guide_dotted_square
// )
// val scaleBitmap = BitmapUtils.scaleBitmap(bgBitmap, rectF.width().toInt(), rectF.height().toInt())
// canvas.drawBitmap(scaleBitmap!!, rectF.left, rectF.top, paint)
}
}
if (true == mHightLight?.isShowing) {
mHightLight?.remove()
}
if (true == mOneModeLightView?.isShowing) {
mOneModeLightView?.remove()
}
if (true == mTwoModeLightView?.isShowing) {
mTwoModeLightView?.remove()
}
val showTwoMode = fun() {
mTwoModeLightView = HighLight(requireContext()).also { twoModeLightView ->
twoModeLightView.autoRemove(false)
.intercept(true)
.addHighLight(R.id.yyyyy, R.layout.xxxxx, object : OnBaseCallback(offest) {
override fun getPosition(rightMargin: Float, bottomMargin: Float, rectF: RectF, marginInfo: HighLight.MarginInfo) {
val width = DensityUtils.dp2px(requireContext(), 80f)
val centerX = rectF.centerX()
marginInfo.leftMargin = centerX - width / 2F
marginInfo.topMargin = rectF.top + rectF.height() + offset
}
}, customShape)
/*下一步*/
.addHighLight(R.id.yyyyy, R.layout.xxxxx, object : OnBaseCallback(offest) {
override fun getPosition(rightMargin: Float, bottomMargin: Float, rectF: RectF, marginInfo: HighLight.MarginInfo) {
marginInfo.leftMargin = rightMargin - rectF.width() / 2
marginInfo.bottomMargin = bottomMargin + offset
}
}, object : BaseLightShape() {
override fun drawShape(bitmap: Bitmap?, viewPosInfo: HighLight.ViewPosInfo?) {
}
override fun resetRectF4Shape(viewPosInfoRectF: RectF?, dx: Float, dy: Float) {
}
})
.setOnShowCallback {
val hightLightView = twoModeLightView.getHightLightView()
/*注意此处 源代码中设置 布局ID 为 ID*/
val twoModeView = hightLightView.findViewById<View>(R.layout.xxxxx)
val next = hightLightView.findViewById<View>(R.id.next)
val nextTv = hightLightView.findViewById<TextView>(R.id.next_tv)
next.setOnClickListener {
twoModeView.visibility = View.GONE
showTwoSeeMode(true)
/*再次点击 关闭*/
nextTv.setText(R.string.know)
next.setOnClickListener {
SharePreferenceUtil.setFirstUserUseTwo(requireContext(), false)
needNewUserGuid = true
twoModeLightView.remove()
twoModePopup?.dismiss()
}
}
}
}.show()
}
val showOneMode = fun() {
mOneModeLightView= HighLight(requireContext()).also { oneModeLightView ->
oneModeLightView.autoRemove(false)
.intercept(true)
.addHighLight(R.id.yyyyy, R.layout.xxxxx, object : OnBaseCallback(offest) {
override fun getPosition(rightMargin: Float, bottomMargin: Float, rectF: RectF, marginInfo: HighLight.MarginInfo) {
Log.d("showOneMode", "" + rectF.width())
Log.d("showOneMode", "" + rectF.height())
Log.d("showOneMode", "" + rectF.top)
Log.d("showOneMode", "" + rectF.bottom)
val width = DensityUtils.dp2px(requireContext(), 224f)
val centerX = rectF.centerX()
marginInfo.leftMargin = centerX - width / 2F
marginInfo.topMargin = rectF.top + rectF.height() + offset
}
}, customShape)
/*下一步*/
.addHighLight(R.id.yyyyy, R.layout.xxxxx, object : OnBaseCallback(offest) {
override fun getPosition(rightMargin: Float, bottomMargin: Float, rectF: RectF, marginInfo: HighLight.MarginInfo) {
marginInfo.leftMargin = rightMargin - rectF.width() / 2
marginInfo.bottomMargin = bottomMargin + offset
}
}, object : BaseLightShape() {
override fun drawShape(bitmap: Bitmap?, viewPosInfo: HighLight.ViewPosInfo?) {
}
override fun resetRectF4Shape(viewPosInfoRectF: RectF?, dx: Float, dy: Float) {
}
})
.setOnShowCallback {
val hightLightView = oneModeLightView.getHightLightView()
val next = hightLightView.findViewById<View>(R.id.next)
val nextTv = hightLightView.findViewById<TextView>(R.id.next_tv)
nextTv.setText(R.string.know)
next.setOnClickListener {
needNewUserGuid = true
oneModeLightView.remove()
}
}
}.show()
}
mHightLight = HighLight(requireContext()).also { highLight ->
highLight.autoRemove(false)//设置背景点击高亮布局自动移除为false 默认为true
.intercept(true)//设置拦截属性为false 高亮布局不影响后面布局的滑动效果
// .enableNext()//开启next模式并通过show方法显示 然后通过调用next()方法切换到下一个提示布局,直到移除自身
//.anchor(drawer_layout)
.addHighLight(R.id.yyyyy, R.layout.xxxxx, OnBottomPosCallback(), customShape)
.addHighLight(R.id.yyyyy, R.layout.xxxxx, object : OnBaseCallback(offest) {
override fun getPosition(rightMargin: Float, bottomMargin: Float, rectF: RectF, marginInfo: HighLight.MarginInfo) {
marginInfo.leftMargin = rectF.right - rectF.width() + offset * 2
marginInfo.bottomMargin = bottomMargin + rectF.height() + offset
}
}, customShape)
.addHighLight(R.id.yyyyy, R.layout.xxxxx, object : OnBaseCallback(offest) {
override fun getPosition(rightMargin: Float, bottomMargin: Float, rectF: RectF, marginInfo: HighLight.MarginInfo) {
val width = DensityUtils.dp2px(requireContext(), 80f)
marginInfo.leftMargin = rectF.right - width - offset
marginInfo.bottomMargin = bottomMargin + rectF.height() + offset
}
}, customShape)
.addHighLight(R.id.yyyyy, R.layout.xxxxx, object : OnBaseCallback(offest) {
override fun getPosition(rightMargin: Float, bottomMargin: Float, rectF: RectF, marginInfo: HighLight.MarginInfo) {
marginInfo.leftMargin = rectF.right - 2 * rectF.width()
marginInfo.bottomMargin = bottomMargin + rectF.height() + offset
}
}, CircleLightShape())
.addHighLight(R.id.yyyyy, R.layout.xxxxx, OnTopPosCallback(offest), CircleLightShape())
/*下一步*/
.addHighLight(R.id.yyyyy, R.layout.xxxxx, object : OnBaseCallback(offest) {
override fun getPosition(rightMargin: Float, bottomMargin: Float, rectF: RectF, marginInfo: HighLight.MarginInfo) {
marginInfo.leftMargin = rightMargin - rectF.width() / 2
marginInfo.bottomMargin = bottomMargin + offset
}
}, object : BaseLightShape() {
override fun drawShape(bitmap: Bitmap?, viewPosInfo: HighLight.ViewPosInfo?) {
}
override fun resetRectF4Shape(viewPosInfoRectF: RectF?, dx: Float, dy: Float) {
}
})
.setOnLayoutCallback {
val hightLightView = highLight.getHightLightView()
hightLightView.findViewById<View>(R.id.next).setOnClickListener {
highLight.remove()
if (true) {
showTwoMode()
} else {
showOneMode()
}
}
}
/* .setClickCallback {
*//*enableNext 为true 生效*//*
highLight.next()
}*/
.show()
}
}
为节省篇幅,粘贴一处Layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/dimmen_80dp"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:orientation="vertical"
tools:background="@color/gray_transparent">
<ImageView
android:id="@+id/arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="@dimen/padding_5dp"
android:src="@drawable/guide_arrow_up" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:lineSpacingExtra="1.5dp"
android:text="@string/tip_mode"
android:textColor="@color/white"
android:textSize="@dimen/textsize_16sp" />
</LinearLayout>
细节如下
- 源代码中将布局设置成layout ID 所以在xml最外层设置ID是会被这个覆盖的,这样能编译通过,但AS会报红,不影响。
hightLightView.findViewById<View>(R.layout....)
- customShape自定义的高亮画布这边,直接奉上,需要注意的是使用shape较好,缩放不会出现多余部分。
customShape->drawShape
- 不画高亮背景
object : BaseLightShape() {
override fun drawShape(bitmap: Bitmap?, viewPosInfo: HighLight.ViewPosInfo?) {
}
override fun resetRectF4Shape(viewPosInfoRectF: RectF?, dx: Float, dy: Float) {
}
}
- 除了OnBaseCallback的四个基础子类,使用自定义的定位器OnBaseCallback,满足不同需求,
//控件 中下方 **控件不处于正中间**
object : OnBaseCallback(offest) {
override fun getPosition(rightMargin: Float, bottomMargin: Float, rectF: RectF, marginInfo: HighLight.MarginInfo) {
val width = DensityUtils.dp2px(requireContext(), 80f)
marginInfo.leftMargin = rectF.right - width - offset
marginInfo.bottomMargin = bottomMargin + rectF.height() + offset
}
}
//控件 中上方 **控件正中间**
object : OnBaseCallback(offest) {
override fun getPosition(rightMargin: Float, bottomMargin: Float, rectF: RectF, marginInfo: HighLight.MarginInfo) {
marginInfo.leftMargin = rightMargin - rectF.width() / 2
marginInfo.bottomMargin = bottomMargin + offset
}
}
//控件 左对齐 上方
object : OnBaseCallback(offest) {
override fun getPosition(rightMargin: Float, bottomMargin: Float, rectF: RectF, marginInfo: HighLight.MarginInfo) {
marginInfo.leftMargin = rectF.right - rectF.width() + offset * 2
marginInfo.bottomMargin = bottomMargin + rectF.height() + offset
}
}
//控件 右对齐 上方
object : OnBaseCallback(offest) {
override fun getPosition(rightMargin: Float, bottomMargin: Float, rectF: RectF, marginInfo: HighLight.MarginInfo) {
val width = DensityUtils.dp2px(requireContext(), 80f)
marginInfo.leftMargin = rectF.right - width - offset
marginInfo.bottomMargin = bottomMargin + rectF.height() + offset
}
}
//控件 左上方
object : OnBaseCallback(offest) {
override fun getPosition(rightMargin: Float, bottomMargin: Float, rectF: RectF, marginInfo: HighLight.MarginInfo) {
marginInfo.leftMargin = rectF.right - 2 * rectF.width()
marginInfo.bottomMargin = bottomMargin + rectF.height() + offset
}
}
//控件 右上方
OnTopPosCallback(offest)
引入
implementation 'com.isanwenyu.highlight:highlight:1.8.0'
工具类如下
- dp2px
fun dp2pxFloat(context: Context, dpVal: Float): Float {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, context.resources.displayMetrics)
}
fun dp2px(context: Context, dpVal: Float): Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, context.resources.displayMetrics).toInt()
}
- 获取布局位置
getLocationInView
https://blog.csdn.net/qq_20330595/article/details/111870246#_1