1、实现效果
效果如下图所示,聚光灯随手指的位置变化而变化,通过进度条控件改变聚光灯大小以及修改背景透明度。
2、自定义控件绘制解析
聚光灯自定义控件,默认为全黑的背景,圆的半径为200f,若要灵活配置,可以声明自定义属性聚光区域圆的半径,修改圆大小的系数等。
下面介绍绘制流程:
canvas.drawRect(rect,paint)
canvas.drawCircle(cx,cy,paint)
canvas.drawBitmap(bitmap,left,top,paint)
onTouchEvent(event:MotionEvent)
上面分析了聚光灯的的绘制流程,OK,现在开始实现。
一、绘制黑色的背景 & 绘制聚光区域圆 & 绘制关闭和设置图标
private var cx = 0f
private var cy = 0f
private var mWidth: Int = 0
private var mHeight: Int = 0
private var radius = 200f
private lateinit var rect:Rect
/**
* 获取关闭图标
*/
private val dismissBp: Bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_dismiss)
/**
* 获取设置图标
*/
private val settingBp: Bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_setting)
/**
* 初始化遮罩画笔
*/
private val maskPaint: Paint = Paint().apply {
strokeWidth = 5f
style = Paint.Style.FILL
color = Color.WHITE
isAntiAlias = true
isDither = true
setLayerType(LAYER_TYPE_HARDWARE,null)
}
/**
* 绘制bitmap画笔
*/
private val bitmapPaint: Paint = Paint().apply {
style = Paint.Style.FILL
color = Color.BLACK
alpha = 255
isDither = true
isAntiAlias = true
isFilterBitmap = true
}
/**
* 获取屏幕宽和高,声明圆的中心坐标,创建rect对象
*/
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
mWidth = w
mHeight = h
cx = mWidth / 2f
cy = mHeight / 2f
rect = Rect(0,0, mWidth, mHeight)
}
/**
* 绘制操作
*/
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawSpotlight(canvas)
}
private fun drawSpotlight(canvas: Canvas) {
canvas.apply {
//绘制黑色背景,未使用drawColor方法是为了后面修改透明度
drawRect(rect,bitmapPaint)
//表示清除所有颜色值和透明通道
maskPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
//绘制聚光区域
drawCircle(cx, cy, radius, maskPaint)
//绘制关闭图标
drawBitmap(dismissBp, mWidth / 2f - dismissBp.width - 20f, mHeight - dismissBp.height - 100f, bitmapPaint)
//绘制设置图标
drawBitmap(settingBp, mWidth / 2f + 20f, mHeight - settingBp.height - 100f, bitmapPaint)
}
}
二、处理触摸事件
触摸事件处理主要考虑以下几点:
/**
* 事件回调
*/
private lateinit var listener:(type:Int) -> Unit
fun onBitmapTapListener(l:(type:Int) ->Unit){
this.listener = l
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
cx = event.x
cy = event.y
if (cx > mWidth / 2f - dismissBp.width //判断按下的点是关闭按钮区域
&& cx < mWidth / 2f - 20f
&& cy > mHeight - dismissBp.height - 100f
&& cy < mHeight - 100f
) {
listener.invoke(ImageType.TYPE_DISMISS)
visibility = GONE
} else if (cx > mWidth / 2f + 20f //判断按下的点是设置区域
&& cx < mWidth / 2f + 20f + settingBp.width
&& cy > mHeight - settingBp.height - 100f
&& cy < mHeight - 100f
) {
listener.invoke(ImageType.TYPE_SETTING)
} else {
invalidate() //通知系统重新绘制
}
}
MotionEvent.ACTION_MOVE -> {
//判断手指滑动的距离最小为4f 再执行移动重绘操作
if(kotlin.math.sqrt((kotlin.math.abs(event.x - cx).toDouble().pow(2.toDouble()) + kotlin.math.abs(event.y - cy).toDouble().pow(2.toDouble()))) > 4f){
cx = event.x
cy = event.y
invalidate()
}
}
}
return true
}
三、设置圆大小和背景透明度方法
/**
* 设置圆的半径
*/
fun setCircleRadius(p:Int){
this.radius = 200f + p * 2
invalidate()
}
/**
* 设置背景透明度
*/
fun setBackgroundAlpha(a:Int){
bitmapPaint.alpha = 255 - 255 / 100 * a
invalidate()
}
将控件【Android自定义控件(一) 可滑动的进度条】 和聚光灯进行结合使用,这里偷了个懒,没有将控件做一个组合控件使用。
Xml:
<com.xn.customview.widget.SpotlightView
android:id="@+id/spotlightView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/clSetting"
android:layout_width="@dimen/px_906"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/px_60"
android:background="@drawable/shape_slide_view"
android:paddingTop="@dimen/px_60"
android:paddingBottom="@dimen/px_60"
android:visibility="gone"
android:focusableInTouchMode="true"
android:focusable="true"
android:clickable="true"
android:layout_marginBottom="@dimen/px_240"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:id="@+id/llAlpha"
android:layout_width="wrap_content"
android:layout_height="@dimen/px_90"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tvAlpha"
android:layout_width="@dimen/px_150"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="end|center_vertical"
android:text="@string/alpha"
android:textColor="@color/black" />
<com.xn.customview.widget.SlideView
android:id="@+id/svAlpha"
android:layout_width="@dimen/px_906"
android:layout_height="@dimen/px_72"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
app:backgroundColor="@color/colorEC"
app:backgroundStrokeW="18"
app:backgroundTotalLen="500"
app:handleRadius="30"
app:progress="0"
app:progressColor="@color/colorGrassGreen"
app:progressStrokeW="18"
app:showProgressText="true"
app:startX="0" />
LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="@dimen/px_90"
android:layout_marginTop="@dimen/px_30"
app:layout_constraintStart_toStartOf="@+id/llAlpha"
app:layout_constraintTop_toBottomOf="@+id/llAlpha">
<TextView
android:id="@+id/tvSize"
android:layout_width="@dimen/px_150"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="end"
android:text="@string/size"
android:textColor="@color/black"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvAlpha" />
<com.xn.customview.widget.SlideView
android:id="@+id/svSize"
android:layout_width="wrap_content"
android:layout_height="@dimen/px_72"
android:layout_marginStart="@dimen/px_30"
app:backgroundColor="@color/colorEC"
app:backgroundStrokeW="18"
app:backgroundTotalLen="500"
app:handleRadius="30"
app:progress="0"
app:progressColor="@color/colorGrassGreen"
app:progressStrokeW="18"
app:showProgressText="true"
app:startX="0" />
LinearLayout>
androidx.constraintlayout.widget.ConstraintLayout>
在Activity
中监听进度条修改回调,将数值通过聚光灯提供的方法设置进去,处理了点击聚光灯关闭图标和设置图标弹出进度和透明度布局操作,这样进度条和聚光灯就结合使用起来。
Activity:
@SuppressLint("ClickableViewAccessibility")
private fun bindListener() {
/**
* 透明度进度条回调进度
*/
mBinding.svAlpha.onProgressChange {
mBinding.spotlightView.setBackgroundAlpha(it)
}
/**
* 圆大小进度条回调进度
*/
mBinding.svSize.onProgressChange {
mBinding.spotlightView.setCircleRadius(it)
}
/**
* 关闭和设置点击回调
*/
mBinding.spotlightView.onBitmapTapListener {
if(it == ImageType.TYPE_DISMISS){
mBinding.clSetting.visibility = View.GONE
} else if(it == ImageType.TYPE_SETTING){
mBinding.clSetting.visibility = if(mBinding.clSetting.isVisible) View.GONE else View.VISIBLE
}
}
/**
* 关闭和设置布局点击事件拦截,处理点击会穿透到聚光灯问题
*/
mBinding.clSetting.setOnTouchListener { _, _ -> true }
}
夜深了,要睡了。