Android UI-薄荷健康尺子
效果
源码:HenCoder-CustomView: HenCoder-三篇自定义View仿写 (gitee.com)
原的
仿的
功能点分析
- 根据尺子的量程 和 分度值 绘制尺子的静态效果
- 内容滑动,计算滑动边界
- 惯性滑动,智能定位
- 计算当前刻度值
- 基准线居中
实现分析
绘制尺子刻度
- 分度值:即最小刻度值,就是在测量仪器所能读出的最小值,指测量工具上相邻的两个刻度之间的最小格的数值
- 绘制尺子刻度,肯定要用到循环,最简单的办法,知道尺子的刻度总数,即可把尺子绘制出来。
- 尺子的刻度数根据 量程和分度值确定。上图尺子同样的量程 有两个分度值,尺子的刻度数完全不同
- 刻度数的计算:量程/分度值。 比如:
- 度量范围20~100,量程是80,分度值是1。 一共80个刻度
- 但凡事总有例外,度量范围0~100,量程100,分度值1,并不是100个刻度,而是一百零一个刻度。
- 0也算一个刻度,0 ~ 100 是101个数。1~100才是100个数。 程序员应该很好理解吧,毕竟从入行开始,数的起始就不是1了🐶
- 尺子的刻度一般都是10进制,则取余数
i % 10 == 0
表明是大刻度,其余的都是小刻度。 - 定义变量,刻度的长款,间隔。已经知道总刻度数,通过循环遍历即可绘制出尺子的静态样式
- 绘制文字比较简单 每次取余数
i % 10 == 0
表示大刻度,需要绘制文字,坐标微调即可。
尺子的滑动
- 滑动分两种,内容滑动和拖拽滑动。
- 内容滑动场景是:ScrollView ,ListView,RecyclerView,在有限的位置固定的空间内可以展示无限内容。
- 拖拽滑动指View内容不变,位置变化。应用场景是微信语音,视频电话的小窗口。
- 尺子的滑动是内容滑动
- 重写
onTouchEvent()
。记录每次手指滑动产生的坐标,上一次坐标与当前坐标相减,计算滑动距离。 - 在move事件中,调用
scrollBy()
传入滑动距离,内容滑动完成
- 惯性滑动组件介绍
- 仅仅使用
scrollBy()
滑动无惯性,效果比较生硬,与系统滑动组件的体验相差比较多 - 结合
VelocityTracker
和Scroller
使滑动产生惯性 VelocityTracker
收集手指滑动路径的坐标用作路程,传入时间,计算出速度。Scroller
滑动辅助类,并不实现View滚动。它的作用好像属性动画,计算一段时间内数字变化, 比如:一秒内从0增长到100。 开发者监听数字变化从而实现动画
- 惯性滑动实现
- 重写
onTouchEvent()
调用VelocityTracker.addMovement(event)
收集手势滑动信息 - 在up事件,
VelocityTracker.computeCurrentVelocity(1000)
计算一秒内滑动距离产生的速度 - 速度计算结果 ,手指左滑 速度正数 ,手指右滑 速度负数。
- 速度正数使坐标增加 ,负数使坐标减少。这里会引发一个问题
- 调用
Scroller.fling()
- fling 参数解析
- startX:开始位置
- minX-maxX:区间 ,根据速度计算x值 的范围在 minX maxX之间
- velocityX 速度的影响
- 比如:手指右滑
- 期望效果 x轴正方向移动 值增加
- 实际效果 速度负数 Scroller.fling动画结果 x轴负方向移动 值减少
- 期望效果与实际效果正好相反 所以速度取相反数 效果正好
- fling总结
- startX开始位置 如 :100
- 受速度影响 计算结果 会从100开始增加或减少。
- 但不是无限增加或减少,计算结果的边界在 minX最小值,maxX最大值 之间
- 调用
invalidate()
触发view重绘, - 重写
computeScroll()
,获取Scroller
动画结果 ,调用scrollTo()
实现内容滚动
滑动边界
滑动边界与view的大小是两个概念
view的内容绘制在canvas上,canvas是一块无限大的画布,View有坐标系,左上角是原点(0,0),惨van无限大,坐标系也是无限大的。
View的宽高则是在无限大的canvas从原点开始圈出一块位置展示内容。
如下图,用户的可视范围只是100*100,但无限大的canvas仍然存在。假设在(200,200)的位置画了一个点,虽然用户看不见,但是它仍然存在。
上一节使用scrollBy()
和scrollTo()
实现内容的滑动
其内部原理是修改View的两个属性mScrollX,mScrollY
。mScrollX,mScrollY
表示内容在X轴Y轴的滚动距离,也可以说是确定View的展示的原点。
举例说明:
- 自定义View,宽高都是100
- 两点坐标确定一个矩形,默认原点(0,0) 由于宽高100,另一点坐标(100,100)。View展示canvas (0,0),(100,100)两点坐标圈出的部分
- 沿X轴移动距离100后,原点坐标(100,0),另一点坐标(200,100)。View展示canvas (100,0),(200,100)两点坐标圈出的部分
所以想要实现View内容滑动的边界,就要限制X轴坐标的取值范围,也就是mScrollX
属性的范围,从0到X。
那么如何计算滑动范围呢?
滑动范围 = 大刻度数大刻度宽 + 小刻度数小刻度宽 + 间隔数*间隔宽
基准线居中
其实这个东西吧 属于会了不难,难了不会,经验问题,不知道的可能想半天也没想出来。
先说结论:基准线x轴坐标 = view宽度/2 + mScrollX
就能达到滚动时居中效果。
分析
- 假设View的宽高都为100
- 画一条长度为10的X轴居中的线段,坐标点(50,10)
- 这条线段只是看起来居中,在view的可视范围(0,0),(100,100)内居中,
- 它并不是画在View上,而是画在canvas,view只是圈出一个范围
- 当内容水平滑动,x值不断改变,线段的坐标也要随着滑动不断变化,才能维持居中的效果
- 代表水平滑动距离的变量是
mScrollX
线段坐标点为 (mScrollX +50,10)
智能定位
业务描述
当滑动到两个刻度之间,四舍五入自动定位到最近的那个刻度,比如:滑动到11.6,分度值是1,左右两个刻度分别是11,12。四舍五入滑动到12。
应用场景
- 惯性滑动后需要智能定位
- up手势之后,如果速度过小,无法出发惯性滑动,则需要智能定位
实现过程
这块挺复杂的,没办法详细说 很容易乱,我的思路不一定是最好的,当作参考
- 核心思路是利用等比例换算。
- 预先知道总滑动距离,知道当前滑动值,能够计算出滑动比例。
- 滑动比例 == 数值比例,通过比例计算出当前的测量值
- 根据分度值单位四舍五入,求出定位值,计算出定位值的X轴坐标
mScrollX
-定位值的X轴坐标 = 滑动距离。求出滑动距离后利用Scroller.startScroll()
进行滑动
只绘制可视区域内容
之前几点完成之后就算是可以正常使用的组件了,原本是不打算做可视区域绘制的(懒)
但是在调试的时候发现绘制内容过多会很卡,不流畅z
比如:度量范围1~100,分度值是1,需要绘制100个刻度。分度值0.1,需要绘制1k个刻度,分度值0.01,需要绘制1w个刻度,卡顿非常明显了,简直不能用。
计算可视区域非常简单。view的可见区域 = x轴坐标范围 = 滚动距离 + view的宽度
x坐标在范围内视为可见,不在范围内视为不可见
private fun isVisibleArea(x: Int): Boolean {
//view的可见区域 = x轴坐标范围 = 滚动距离 + view的宽度
val offset = 20 //偏移量
val start =scrollX-offset
val end =scrollX+measuredWidth+ offset
return x in start..end
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//简化代码
if (isVisibleArea(x)){
drawLine(i, canvas)
drawText(i, canvas)
}
}
链接:https://juejin.cn/post/7095286970976305182
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。