一个文件实现安卓滚轮选择控件

来源:互联网 发布:美工海报设计兼职 编辑:程序博客网 时间:2024/05/21 09:53

这一次跟大家分享的是一个非常轻量的滚轮选择控件,只有1个文件,一共400多行代码,源码已经上传到github: 
https://github.com/huzenan/EasyPickerView

我们知道如果想在项目里,尤其是大的项目里使用别人造好的“轮子”,假如说我只想用一个好看的滚轮选择控件,而必须引入好几个文件甚至十几个文件,那真的是很不情愿的。这个时候,非常轻量级的EasyPickerView就能解决问题了,只要一个文件,代码也很少,心情好了还能做些修改以适应自己的需求,还不是美滋滋的。


先看看效果

easypickerview


用法

效果还是很棒棒的,用法也很简单,首先是布局文件:

xml

支持以下几个属性: 
- epvTextSize:字符的大小 
- epvTextColor:字符的颜色 
- epvTextPadding:字符的间距 
- epvTextMaxScale:中间字符缩放的最大值 
- epvTextMinAlpha:两端字符最小alpha值 
- epvRecycleMode:是否为循环模式 
- epvMaxShowNum:显示多少个字符

看完会发现,如果最后一个属性epvMaxShowNum设置了偶数会怎么样呢,效果如下:

easypickerview

设置为偶数的时候,字符上下各显示一半。

然后就是在使用时设置一下数据集,再设置一下监听了:

listener


原理

整个View的原理,是在触摸过程中改变一些属性值,然后重绘整个View来达到滚动的效果。过程中用于控制滚动效果的属性值包括: 
- curIndex:当前选中项 
- offsetIndex:本次滚动中偏移的项 
- offsetY:本次滑动的Y坐标偏移量 
- oldOffsetY:在fling之前的offsetY

后面会解释这几个属性值的作用。


1.测量

首先是测量,宽度的测量比较简单,match_parent模式下的宽度直接使用父布局给的建议宽度,wrap_content模式下的宽度为:

宽度 = 最大字符宽度 + 左右padding

其中“最大字符宽度”需要根据设置的数据集进行更新,以数据集里最宽的那个数据的宽度为准。

然后是高度的测量,match_parent模式下使用建议高度,wrap_content模式下,首先计算单个字符高度,然后计算内容区域的高度:

内容高度 = (单个字符高度 + 字符间距) x 显示的字符数

这里为啥字符间距不是 (显示的字符数 - 1) 个呢,其实是为了在上下两端各留出一半字符间距的距离,这样在滑动的时候不会显得很突兀(可以给上下两端的字符一个消失前缓冲的空间),直接看下图解比较清晰:

这里写图片描述


2.触摸事件

由于要实现类似ListView的fling快速滑动的效果,我们需要使用Scroller和VelocityTracker,简要的讲,Scroller用于跟踪滑动的轨迹,VelocityTracker用于跟踪触摸点的速度。

我们重写onTouchEvent方法,每一个触摸事件都要跟踪触摸点的速度,因此最开始先更新一下VelocityTracker:

addVelocityTracker

然后看下ACTION_DOWN

ACTION_DOWN

当手指按下时,若还处于滚动状态,则先结束滚动,包括让Scroller停止对轨迹的跟踪,和停止整个View的滑动(finishScroll方法做一些恢复操作),然后记录按下点的Y坐标值。finishScroll方法在ACTION_UP中也有调用,因此等到下面再讲。

接着是ACTION_MOVE

ACTION_MOVE

这里使用offsetY来记录手指距离按下点的Y坐标的偏移量(向上滑动时offsetY为负数,向下滑动时offsetY为正数),并使用isSliding来记录本次触摸事件是否触发了滑动状态,若没有则在手指抬起时判定为一次点击事件。首次move事件时isSliding为false,此时需要手指滑动距离大于一个阈值时才开始滑动,这个阈值即scaledTouchSlop,同样的也限制了最大以及最小的滚动速度(后面用到),这几个值都可以通过ViewConfiguration获取系统建议的值,以保持和系统一样的风格:

ViewConfiguration

之后的move事件,由于isSliding设置为true,因此不需要再判断阈值了。然后调用了reDraw方法:

reDraw

reDraw方法首先计算了当前选中项curIndex在该事件过后需要偏移的量,为整数值,看图解清晰些:

reDrawPic

如上图得到的偏移量 i 值即为2,接着判断,如果是循环模式则可以直接更新offsetIndex,否则还需要计算偏移过后是否在数据集的范围内,是才更新offsetIndex。然后通过postInvalidate方法刷新视图。若以上条件都不满足,则直接结束滚动。

最后是ACTION_UP

ACTION_UP

获取Y方向的速度getScrollYVelocity方法很简单:

getScrollYVelocity

最后将isSliding设置为false表示本次触摸已经结束,接着回收VelocityTracker:

recycleVelocityTracker

可是为啥这样就能让整个View做到像ListView那样的fling效果呢,原因是我们调用了Scroller的fling方法,把计算出来的Y方向上的速度作为初速度传递给了Scroller,且将Y方向上的值设置为0,并且调用了invalidate进行了重绘,此时会回调View的一个方法computeScroll:

computeScroll

我们在ACTION_UP时记录了fling开始前的偏移量oldOffsetY,再加上Scroller的getCurrY方法获取计算出来的当前滑动的偏移量,即为新的offsetY。

reDraw已经看过,现在来看看finishScroll:

finishScroll

步骤有点多,但都是一些判断与计算,首先是结束后的停留位置(需要向上还是向下滚动多一行),v 值计算的是结束时offsetY不满一个padding的距离,还是看看清晰的图解:

finishScrollPic

计算出 v 值后会判断,如果大于 centerPadding/2 ,则说明是向下滑动,且超过了centerPadding的一半,需要在当前偏移量上加1,否则如果小于 -centerPadding/2 ,则说明是向上滑动,并且也超过了centerPadding的一般,需要在当前偏移量上减1。

下一步会重置当前选中项,看看重置的方法:

getNowIndex

很简单的计算,循环模式下,用模除计算出此时的选中项,非循环模式下限制滚动的范围即可。

接下来是计算回弹的距离,也可以说是纠正滑动距离,这里用最终计算得到的offsetIndex(index此时已纠正过),乘以centerPadding得到完整的偏移距离,再减去offsetY就得到需要纠正的偏移距离。当然这只是一种计算方法,也可以使用 v 值来计算,很随意的。

然后是更新我们的监听接口,接着reset重置上面提到的四个属性值,最后调用postInvalidate重绘。

至于moveTo,原理和处理move事件相同,只不过此时调用的是Scroller的startScroll方法而不是fling方法。而moveBy更简单,传入的是偏移的数量,再通过getNowIndex计算得到此时的选中项,然后直接调用moveTo方法即可。

就这样,我们在处理触摸事件的过程中,更新了偏移量和选中项,这些数据将在每一次绘制的时候,决定着某一个数据项应该绘制在什么位置。


绘制

所有属性值相关的操作都讲完了,接下来只剩下绘制了,重写onDraw方法,我们分步来看看。

首先是裁剪出我们的内容区域,后面在绘制字符时会讲为什么需要裁剪,这里cx和cy分别是整个View的中心点x和y的坐标值,在测量后就可以得到:

clipRect

接着开始绘制字符。首先计算出数据集长度size,centerPadding为每一行字符加上padding占多高 ,至于half参数,是为了实现中间向两端滑动时逐渐变小的效果,以中间项为标准,分别往两端计算:

drawText

接着开始遍历数据集,并根据以上的参数,把范围内的数据绘制到屏幕中:

drawTextFor

计算稍微有点酸爽,我们一个个看。首先是遍历的范围,看看index的取值,我们假设最多显示3个,curIndex为0(可以忽略不看offsetIndex,因为其作用只是在本次触摸中将curIndex增加一个偏移量而已),此时index取值范围是-2到2,上下两端各多出1个,这就是为什么需要剪裁了,保证多余的不显示在区域内,并且保证在滑动时两端能有字符滑入显示区域内,看下图解:

clip

接着判断如果是循环模式,则在超过数据集范围时更新下index,然后开始绘制字符,其中scale表示在1.0基础上,增加的textMaxScale的百分比,然后设置一下画笔,再绘调用Canvas的drawText方法制字符即可。这里drawText方法的第三个参数为字符的baseLine,若想将字符以某个位置居中显示,baseLine可以这么算:

baseLine = 绘制的中点的Y坐标 - (上坡度 + 下坡度) / 2

具体的概念就不展开来讲了,网上有很多文章都有具体的讲解,可以学习学习。


总结

总结一下,整个View的核心原理就在于处理偏移量和选中项,然后通过这几个属性进行绘制,因此无论什么样的View,制定好规则,只要根据这样的思路都能很轻易很快速地实现了。

欢迎拍砖,也欢迎关注我的github: 
https://github.com/huzenan