每当UE给一个他们认为完美的交互时候,我们第一想法是卧槽,这种交互好像我没见过,有没有开源的,好不好做。
关于ViewPager的导航,网上的自定义控件非常多,大部分能够满足我们的需求,而且每一个开源出来的控件都有非常多的自定义属性,尽量让使用者可以更加全面的控制样式。但是如果没有我们需要的样式呢?我们还是深入了解一下它的原理吧。
常用示例
以下为慕课网APP中的两个应用场景。
通过以上可以看到主要的有如下几点:
- 选中效果
- 滚动居中
- 辅助指示(下划线等)
- 滚动反馈(动态改变状态栏颜色)
原理解析
针对上面所说的特点进行分别说明,整体的实现方案非常多,但思想都是大同小异,这里我们选取HorizontalScrollView
嵌套LinearLayout
,LinearLayout
中添加TextView
的方案来进行说明。
其中选中效果比较好理解,设置选中位置文本颜色、文本大小等等的变化。
ViewPager滑动监听
我们都知道ViewPager
提供了addOnPageChangeListener
方法可以用来监听滑动翻页。
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, @Px int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
针对每个方法来具体说明下:
-
onPageSelected 有一个参数position,我们使用的比较多,就是被选中的位置。
-
onPageScrollStateChanged
其中status有如下三个状态:- SCROLL_STATE_IDLE(静止状态)
- SCROLL_STATE_DRAGGING(拖动状态,手指按下并滑动)
- SCROLL_STATE_SETTLING(释放状态,手指按下并滑动后抬起手指惯性滑动的一段。)
-
onPageScrolled
onPageScrolled
方法也是非常重要而且不太好理解的回调方法,而且有三个参数:- position 源码中的注释是
Position index of the first page currently being displayed.
在滑动翻页的过程中,我们是可以看到左右两页,这里的position就是左页的位置。这里很重要,后面两个参数也是相对于该位置来说的。 - positionOffsetPixels 滑动过程中左页的位移(单位像素)
- positionOffset 滑动过程中左页的位移与最大位移的比例 [0~1)
- position 源码中的注释是
通过以上说明,对回调的方法有了大致的了解,然后大家可以根据下图来进行体会下,这几个回调方法很重要,每个参数都要清晰地理解。
滚动居中
如下图所示,怎么在由云计算/大数据
--> 前端开发
翻页的时候,导航跟随居中呢?
分析:
- 翻页前
云计算/大数据
Tab居中; - 翻页过程中跟随ViewPager滑动Tab动态滚动;
- 翻页后
前端开发
Tab居中。
由于从云计算/大数据
-->前端开发
翻页,这里我们定义云计算/大数据
为第一页Tab即firstPageTabView,前端开发
为第二页Tab即secondPageTabView。
很容易得到,firstPageTabView中间到控件左边的距离:firstPageTabView距离左边距离 + firstPageTabView的宽度一半。需要将firstPageTabView的中间滚动到整体控件的中间,那么滚动的距离就是:firstPageTabView中间到控件左边的距离 - 控件宽度的一半。滑动开始前需要向左滚动的距离为:
同理,滑动到第二个时:
由于
所以滑动到第二个时也可以表示为:
但这只是计算了选择状态下需要滚动的距离,那么怎么得到需要动态滚动的距离呢?视乎还差的很远,其实对比一下开始和结束的距离:
对比发现,结束比开始多了firstPageTabView.getWidth()
,firstPageTabView.getWidth() / 2
变成了secondPageTabView.getWidth() / 2
,之前我们在ViewPager滑动监听中提到一个重要的回调方法onPageScrolled
,该方法中positionOffset
就是滚动距离的比例。我们变换下来推导下滑动过程的偏移量。
通过以上推导就得到了滑动过程的计算方法,当然滑动过程的技术方法还可以进一步的整理优化。
最终得到的滚动方法如下:
void scrollToSelectedTab(int firstPagePosition, float positionOffset) {
int childCount = mSlidingTabStrip.getChildCount();
if (childCount == 0) {
return;
}
View firstPageTabView = mSlidingTabStrip.getChildAt(firstPagePosition);
float offset = firstPageTabView.getWidth() * positionOffset;
float scrollX = getPaddingLeft() + firstPageTabView.getLeft() + offset - getWidth() / 2;
float left = 0;
float right = 0;
if (firstPagePosition < childCount - 1) { // Sliding the page.
View secondPageTabView = mSlidingTabStrip.getChildAt(firstPagePosition + 1);
left = firstPageTabView.getLeft() + positionOffset * (secondPageTabView.getLeft() - firstPageTabView.getLeft());
right = firstPageTabView.getRight() + positionOffset * (secondPageTabView.getRight() - firstPageTabView.getRight());
} else if (firstPagePosition == childCount - 1) { // After selected the last page.
left = firstPageTabView.getLeft();
right = firstPageTabView.getRight();
}
scrollX += (right - left) / 2;
scrollTo((int) scrollX, 0);
}
源码及示例
github地址:SlidingTabLayout