几个重点问题的分析
问题是怎么发现的?
在开源库的基础上,对参数及关键方法做出一些改动,没有达到自己预期的效果。后来才发现是有些内容没有摸透,在研究代码的过程中,始终抱着一个疑问,那就是—作者为什么要这么做。在磕磕碰碰后,终于能理解大致的思路,在这里将重点问题做出分析。
1.ViewPagerListener调用的时机
根据ViewPager的源码分析,该Listener会在两个时机被调用
- OnPageSelected—对应的源码为dispatchOnPageSelected
- OnScrollStateChanged—对应的源码为dispatchOnSrollStateChanged
tab的普通滑动模式
核心点 1:确定tab滑动的距离
这里,我们假定是selectedTabWidth,也就是当前tab的宽度。
作者在代码中选择selectedTab的start点来作为参考点
int start=Utils.getStart(selectedTab);
x+=start-startMargin+extraOffset;
scrollTo(x,0);
start也就是该视图的左边界,相对于其父视图的位置(视图坐标)
那么,tab每次滑动的初始点,都是相对于自己getLeft()值
问题:如果上一个tab滑动后的结束位置和当前tab的起始位置不同,那么就会出现”闪现”—在滑动结束后,突然滑动一大段距离
核心点 2: 确定strip的滑动距离
strip的本质是在父视图上画一个Rect,只要能确定出Rect的left和right就行了。
起始 left=selectedStart
起始 right=selectedEnd
至于,在移动过程中的left和right的确定,是由不同的interpolater确定的
float startOffset = indicationInterpolator.getLeftEdge(selectionOffset);
float endOffset = indicationInterpolator.getRightEdge(selectionOffset);
int nextStart = Utils.getStart(nextTab, indicatorWithoutPadding);
int nextEnd = Utils.getEnd(nextTab, indicatorWithoutPadding);
left = (int) (startOffset * nextStart + (1.0f - startOffset) * left);
right = (int) (endOffset * nextEnd + (1.0f - endOffset) * right);
为了确定left和right公式的正确性,只需要将0和1两个极端值带进去,得出的结果是正确的就ok了
left的确定
StartOffset | left |
---|---|
0 | left(起点) |
1 | nextStart(终点) |
right的确定
RightOffset | right |
---|---|
0 | right(起点) |
1 | nextEnd(终点) |
TabStrip的Smart滑动
该效果是由特定的Interplator实现的,如这里的SmartTabIndicationInterpolator
public SmartIndicationInterpolator(float factor) {
leftEdgeInterpolator = new AccelerateInterpolator(factor); //加速器
rightEdgeInterpolator = new DecelerateInterpolator(factor);//减速器
}
@Override
public float getLeftEdge(float offset) {
return leftEdgeInterpolator.getInterpolation(offset);
}
@Override
public float getRightEdge(float offset) {
return rightEdgeInterpolator.getInterpolation(offset);
}
在滑动的过程中,offset为两个ViewPager之间滑动的比例,范围为0f-1f,strip的left由加速插值器决定,right由减速插值器决定。
Tab滑动—Title offset Auto Center
该模式的关键点在于,如何确定strip在布局中的中点,不同长度的strip对应的中点也是不同的
1.先确定中点
先这样假设,假如我们要滑动的是一个点,那么所谓的中点肯定是在getWidth()/2
,但是,现在是一条线段,他自己也是有长度的。
x = Utils.getWidthWithMargin(selectedTab) / 2 - getWidth() / 2;
这段代码其实算出的是该strip在布局中左顶点坐标。
2.确定每次滑动的距离
假设这样一个场景,你此时已经滑动到了中点,那么怎样保证在接下来的滑动中,strip一直处于中间呢?
诀窍就是:strip向右滑动了多少,整个tab便向左滑动多少
int selectHalfWidth = Utils.getWidth(selectedTab) / 2 + Utils.getMarginEnd(selectedTab);
int nextHalfWidth = Utils.getWidth(nextTab) / 2 + Utils.getMarginStart(nextTab);
int extraOffset = Math.round(positionOffset * (selectHalfWidth + nextHalfWidth));
extraOffset便是strip向右滑动的距离。
这句代码用来设置x的临界值,假如,屏幕上显示了4个tab,每个长度为100,假如,此时在滑动第一个tab,那么x=50-200=150,就算该tab完全滑动完,也还没到临界值,此时滑动第二个tab,此时,x还是为150,注意,对于整个tab而言,已经滑动了100(tab1的宽度)的距离,所以在第二个tab滑动到50的时候,就到达临界值,此时tab开始滑动。
Tab滑动—Always in center
在该模式下,第一个tab必须放在整个布局的中间位置。