导航View的事件分发逻辑
城市导航图示
城市导航控件,是结合事件分发机制及功能需求,来实现绘制出的自定义View。
当在左侧城市列表页面滑动浏览时,右侧导航省、直辖市列表会跟随联动,并根据左侧滑动区间显示选中状态。右侧通过手势滑动,左侧会有View提示及城市列表联动。
事件消费,处理绘制+手势
在自定义该城市导航View中,主要涉及对事件消费处理。及绘制逻辑处理。
主要伪代码如下 ~
/** 自定义view,城市导航组件 - CityViewIndicator.kt */
class CityViewIndicator : View {
.... ....
... ...
/** 初始化操作 */
private fun initView() {
// 抗锯齿
paint.isAntiAlias = true
}
... ... .
/** 执行绘制 */
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.let {
if (list.size > 0) {
onIndicatorDraw(it)
}
}
}
// 绘制逻辑处理
private fun onIndicatorDraw(canvas: Canvas) {...}
// 事件消费处理
override fun onTouchEvent(event: MotionEvent?): Boolean {...}
... ... ...
}
事件消费处理
在该事件消费处理中,核心算法是选择在哪里(什么方法中)拦截用户操作事件。且如何将手指滑动时的坐标转化为列表中省、直辖市的下标。
事件的消费处理核心算法写在dispatchTouchEvent
中也是ok的。鉴于CityViewIndicator
作为View层次中最底层且需要捕获并消费事件的控件,在onTouchEvent
中处理消费逻辑,是更容易理解和考虑到的。
13行,
事件(手指触到)y坐标 与 当前CityViewIndicator的高度比值。计算出当前省、直辖市对应List坐标。
11行
以oldCheckIndex
预先记录下已选中省、直辖市List对应坐标。13行
计算出的下标在18行
赋值给oldCheckIndex
,且赋值前会在15行
判断是否是相同操作,以防止重复绘制。
/** 事件消费处理 */
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
when(it.action) {
MotionEvent.ACTION_DOWN -> {
// 导航弹窗提示-显示 [对外开放接口]
this.indicatorTouchListener?.winDialogChange(true)
}
MotionEvent.ACTION_MOVE -> {
// 滑动
var oldCheckIndex = checkedIndex // 防止重复
// 当手指摁下去时,触摸到的坐标 转化为 选中item的下标
val localIndex = (event.y / viewHeight * list.size).toInt()
if(oldCheckIndex != localIndex) { // 防重复判断
// 向外传递数据,传递省份-直辖市 [对外开放接口]
this.indicatorTouchListener?.valueDelivery(list[localIndex])
checkedIndex = localIndex
invalidate() // 刷新 [只执行onDraw,不执行onLayout]
}
}
MotionEvent.ACTION_UP -> {
// 导航弹窗提示-隐藏 [对外开放接口]
this.indicatorTouchListener?.winDialogChange(false)
}
}
}
return true
}
绘制导航文本
在该绘制逻辑处理中,核心的算法逻辑在如何绘制省、直辖市的文本列表。
准备 ~
1,创建Paint画笔实例;
2,通过canvas画布执行绘制;
文本绘制逻辑 ~
绘制当前省、直辖市列表文本思维 :
在自定义View —— CityViewIndicator
本身一定宽度条件下,开始绘制(画)起笔[Paint]
应该落在画布[Canvas]
什么位置[坐标]
?
16行,
通过paint.measureText(txt)
计算文本宽度,使用自定义View本身宽度减去文本宽度。此时得到的值,是文本在view中左右未占据的空间宽度之和。那么一分为二的值,则是左侧下笔的起始坐标。且文本始终会居中显示~
private fun onIndicatorDraw(canvas: Canvas) {
itemHeight = viewHeight / list.size // 获取每条item,应该分配到的高度
list.forEachIndexed { index, txt ->
if (index == checkedIndex) {
paint.color = checkedTxtColor
paint.textSize = checkedTxtSize
paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD))
} else {
paint.color = nocheckedTxtColor
paint.textSize = nocheckedTxtSize
paint.setTypeface(Typeface.DEFAULT)
}
// 开始绘制文本
val txtX = (viewWidth - paint.measureText(txt))/2
val txtY = ((1 + index) * itemHeight).toFloat()
canvas.drawText(txt, txtX, txtY, paint)
}
canvas.drawARGB(80, 211, 211, 211) // 绘制透明灰色背景
}