在使用信鸽的时候看到ListView的scorllBar加上了个标签,出于好奇,相对此一探究竟,看看是如何实现这种效果的。效果如图:
确定算法
翻看ListView和继承父类的API,并没有发现如getScrollBarHeight(),getScrollBarOffset()之类的API,但是发现有computeVerticalScrollExtent(),computeVerticalScrollRange(),computeVerticalScrollOffset()这几个API,借助这几个API可以间接计算出scrollBar的高度和偏移量。
API介绍:
computeVerticalScrollExtent():scrollBar滚动当前一屏内容需的偏移量
computeVerticalScrollRange():scrollBar偏移量的范围
computeVerticalScrollOffset():scrollBar当前的偏移量
初看这几个API的注释我也感觉似懂非懂,所以,还是去源码一探究竟吧。
computeVerticalScrollRange():
1 | protected int computeVerticalScrollRange() { |
上面的代码计算出scrollBar的偏移量范围为itemCount*100。
computeVerticalScrollExtent():
1 | protected int computeVerticalScrollExtent() { |
上面计算的是滚动当前一屏内容(更准确的说是当前屏幕显示的item)的偏移量。
extent += (top * 100) / height,top是负值,要减去在屏幕顶部外面的部分。同理要减掉在屏幕底部外面的部分。
computeVerticalScrollOffset():
1 | protected int computeVerticalScrollOffset() { |
上面的代码是计算的是scrollBar的已经滚动的偏移量。
简化下range、extent、offset的计算过程:
- range=mItemCount*100
- extent=count100+(top100)/height-(bottom-getHeight())*100/height
- offset=firstPosition100-(top100)/height
从上面的计算看出,100只是个因子,可以是任何数,这里的作用是在保留除法结果的两位小数。
在屏幕上的scrollBar:
height=getMeasuredHeight()*computeVerticalScrollExtent()/computeVerticalScrollRange()
getMeasuredHeight()为ListView测量后的高度
offset=computeVerticalScrollOffset()*(getMeasuredHeight()-heigh)/(computeVerticalScrollRange()-computeVerticalScrollExtent())
为什么offset不等于computeVerticalScrollOffset()/computeVerticalScrollRange()?其实一般情况下,这个算法和上面计算的结果是一样的,但是系统对scrollBar的真实height做了限制,最小值为scrollBar的Drawable的宽度的2倍。(可以参考ScrollBarUtils和ScrollBarDrawable)
对上面的结论来证明下:
1 | size=getMeasuredHeight(); |
上面的计算是基于thumbHeight=sizeextent/range计算得出时,thumbOffset=size(offset/range)。反之则不成立。
上面是分析算法,项目中的话可以用ScrollBarUtils更方便,也更准确。
附上例子:
https://github.com/wslaimin/ExtendedListView.git