Android制作一个锚点定位的ScrollView
因为遇到了一个奇怪的需求:将垂直线性滚动的布局添加一个indicator。定位布局中的几个标题项目。为了不影响原有的布局结构所以制作了这个可以锚点定位的scrollview,就像markdown的锚点定位一样。所以自定义了一个scrollview实现这个业务anchorpointscrollview
完成效果图
需求分析
怎么滚动?
一个锚点定位的scrollview。在scrollview中本身有smoothscrollby(int,int)、scrollto(int,int)这种可以滚动到指定坐标位置的方法。我们可以基于这个方法来进行定位view的位置。
smoothscrollby(int,int)是增量滚动。即从当前位置增加减少滚动距离。
scrollto(int,int)是绝对坐标滚动。滚动到指定的坐标位置。
这里我选择的是使用smoothscrollby这个方法来进行处理。
滚动到哪里?
我已经确定使用smoothscrollby来进行布局的滚动。那么下一步就是要知道滚动到下一个view要多少距离,怎么确定下一个view的坐标位置。
首先要确定view的位置。如果我们通过view.gety()获取的话这个是绝对不正确的。因为view.gety()是当前view与自己父view的嵌套坐标关系。而scrollview内部是个linearlayout,而且布局中也有很多的嵌套关系,所以不能使用view.gety()来获取view的坐标。
使用getlocationonscreen(intarray)获取view在屏幕上的绝对坐标位置,再减去scrollview的绝对坐标位置,就得到了。当前view与scrollview的相对位置关系。它们之间的差值就是我们要滚动的距离。
代码实现
我们写一个方法,让scrollview滚动到指定的view位置。
这里的offset参数是滚动的额外偏移量。来保证滚动的时候预留一些额外空间。
现在已经可以滚动到指定的view位置了。接下来就是比较难的了。
锚点变化位置处理
现在只是能够滚动到指定的view了,但是这并不能完全满足业务需求。在ui上是要有一个indicator指示器的,来指示当前已经滚动到哪个位置。
所以我们先增加一个集合,来保存滚动的锚点view。
并增加方法添加views
分析: 我们已经有了需要定位,需要监听变化的views,当scrollview滚动的时候,我们可以通过onscrollchangelistener监听滚动,并获取注册的锚点view的位置改变信息。在onscrollchange中计算滚动偏移和滚动到哪个view。
在注册onscrollchangelistener的时候我们也要保留外部的监听器使用。
我们接下来的所有操作都将会在computeview()这个方法中进行
我们先封装一个数据体用于保存view与坐标的对应关系。
在onsizechanged的时候,获取当前scrollview的坐标位置
这里的[mpos]在之后都将表示当前scrollview的坐标位置
查找最近两个view
我们该如何确定哪个view滚动的位置已经临近mpos了。我们可以使用一个简单的查询算法来找到。
演示
我们可以遍历view的y坐标与当前的y坐标进行对比然后得到当前y坐标临近的两个值。 我们通过一个测试方法演示一下
大家也可以自己运行一下例子修改tag的大小来验证一下。
我们通过这个简单的算法,抽象的应用到我们的业务逻辑中。
我们通过上面的计算,拿到了当前坐标mpos与之相邻的前一个viewpos和后一个viewpos,而且也得到了滚动到了哪个下标位置index。如果在当前滚动位置之前没有所注册的view即为null。如果在当前滚动位置之后没有所注册的view即为null。
现在我们有了这几个信息参数:
- mpos: 当前滚动布局scrollview的顶部坐标.
- previousview:当前滚动位置的前一个view,或者说是y坐标小于mpos的最近的view。
- nextview:当前滚动位置的下一个view,或者说是y坐标大于mpos的最近的view。
- scrollindex: 即当前滚动到哪个注册的view范围之内了。这个参数的改变周期是,当下一个nextview成为previousview之前,这个值将一直为当前previousview的下标位置。
计算距离
计算previousview与mpos的距离,nextview与mpos的距离. 这个距离其实很好计算。直接拿两个坐标相减即可得到。
这里的代码,在计算滚动距离的时候,要先进行view==null的判断。因为如果是null的话,有两种情况。
- 开始滚动时还未滚动到,注册的第一个view时。第一个view为nextview。previousview==null。
- 滚动到底部了,在滚动下去,后面没有注册的锚点了,最后一个view为previousview,nextview==null
在计算出距离的同时对scrollindex的坐标位置也进行修复。如果还没滚动到第一个注册的锚点view,那么scrollindex=0,如果没有nextview了说明到最后了,scrollindex=最后。还有一种情况就是由于最后一个注册的锚点view的高度,根本不够滚动到scrollview顶部的话。就对这个下标位置进行修复。我们在一开始查找相邻两个view的时候就将isscrollbottom参数进行了初始化。而isfixbottom我们根据业务需求进行设置。
计算距离最终得到了两个参数:
~ previousviewdistance:previousview与mpos的距离。
~ nextviewdistance: nextview与mpos的距离。
计算百分比
有了相隔的距离,接下来我们就可以去求向上滚动时previousview的逃离百分比与nextview的进入百分比。
前一个view的逃离百分比previousratio的值= previousviewdistance/前一个view与下一个view的距离
而下一个view的进入百分比nextratio=1.0-prevousratio.
代码
经过上面的计算我们得到了这几个数据:
- viewdistancedifference:previousview与nextviewy坐标之差。即前后相距的距离
- previousratio:前一个view的逃离百分比,previousview与mpos的距离百分比。
- nextratio:下一个view的进入百分比,nextview与mpos的的距离百分比。
这样就算是完工了。
回调监听
最后我们将这些参数进行分类,交给页面去处理。
增加一个interface
将数据填入
最后再看一眼完成的效果
这里的indicator用的是magicindicator。代码都再github上了。大家自己观摩一下吧。
其实还是有很多优化的空间的。比如查找最相邻的两个view时的算法。在最后注册的1-3个view不足以滚动到顶部的时候,可以让index的变化更加优雅等等。。有待改进。
以上就是android制作一个锚点定位的scrollview的详细内容,更多关于android 制作scrollview的资料请关注其它相关文章!