import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.RectF
import android.util.AttributeSet
import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import androidx.core.graphics.createBitmap
import com.blankj.utilcode.util.GsonUtils
/** 高亮引导 */
class HighlightGuideView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
companion object {
private const val TAG = "HighlightGuideView"
private const val ANIMATION_DURATION = 200L
private var isShow = false
@JvmStatic
fun show(
context: Context,
highlightView: View,
radius: Float = context.resources.getDimension(R.dimen.common_dp_28),
onDismiss: (() -> Unit)
) {
if (isShow) return
isShow = true
HighlightGuideView(context).show(
highlightView,
FragmentGuideBinding.inflate(LayoutInflater.from(context)).root,
radius,
onDismiss
)
}
}
private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = ContextCompat.getColor(context, R.color.main_highlight_guide_bg)
style = Paint.Style.FILL
}
private val highlightPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.TRANSPARENT
xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
private var highlightRect = RectF()
private var highlightView: View? = null
private var cornerRadius = 0f
private var onDismissListener: (() -> Unit)? = null
private var bitmap: Bitmap? = null
private var myCanvas: Canvas? = null
init {
setWillNotDraw(false)
setLayerType(LAYER_TYPE_SOFTWARE, null)
(layoutParams as? LayoutParams)?.gravity = Gravity.CENTER
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (w <= 0 || h <= 0) {
bitmap?.recycle()
bitmap = null
myCanvas = null
return
}
bitmap = createBitmap(w, h)
bitmap?.let {
myCanvas = Canvas(it)
invalidate()
}
Log.i(TAG, "onSizeChanged width $w height $h")
}
/**
* 显示高亮引导
* @param highlightView 要高亮的View
* @param cornerRadius 圆角半径(当shape为ROUNDED_RECT时有效)
* @param onDismiss 关闭回调
*/
fun show(highlightView: View, contentView: View?, radius: Float, onDismiss: (() -> Unit)) {
this.highlightView = highlightView
this.cornerRadius = radius
this.onDismissListener = onDismiss
val rootView = highlightView.rootView
if (rootView !is ViewGroup) {
Log.w(TAG, "rootView not view group")
return
}
rootView.findViewWithTag<HighlightGuideView>(TAG)?.let {
rootView.removeView(it)
}
tag = TAG
contentView?.let { addView(it) }
rootView.addView(
this,
ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
)
calculateHighlightRect(highlightView)
animateShow()
Log.i(TAG, "show")
}
private fun calculateHighlightRect(highlightView: View) {
val location = IntArray(2)
highlightView.getLocationOnScreen(location)
highlightRect.set(
location[0].toFloat(),
location[1].toFloat(),
location[0] + highlightView.width.toFloat(),
location[1] + highlightView.height.toFloat()
)
Log.i(TAG, "calculateHighlightRect ${GsonUtils.toJson(highlightRect)}")
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (myCanvas == null) {
Log.w(TAG, "myCanvas is null")
return
}
if (bitmap == null) {
Log.w(TAG, "bitmap is null")
return
}
myCanvas?.drawRect(0f, 0f, width.toFloat(), height.toFloat(), bgPaint)
myCanvas?.drawRoundRect(highlightRect, cornerRadius, cornerRadius, highlightPaint)
bitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) }
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
if (!highlightRect.contains(event.x, event.y)) {
dismiss()
}
}
MotionEvent.ACTION_CANCEL -> {
dismiss()
}
}
return true
}
fun dismiss() {
animateDismiss()
}
private fun animateShow() {
alpha = 0f
animate()
.alpha(1f)
.setDuration(ANIMATION_DURATION)
.setInterpolator(AccelerateDecelerateInterpolator())
.start()
}
private fun animateDismiss() {
animate()
.alpha(0f)
.setDuration(ANIMATION_DURATION)
.setInterpolator(AccelerateDecelerateInterpolator())
.withEndAction {
(parent as? ViewGroup)?.removeView(this)
onDismissListener?.invoke()
}.start()
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
Log.i(TAG, "onDetachedFromWindow")
isShow = false
bitmap?.recycle()
bitmap = null
myCanvas = null
}
}