原文地址
https://blog.davidmedenjak.com/android/2017/06/24/viewpager-recyclerview.html
先贴最后的效果图,所有完整的代码可以在这里找到,觉得不错的可以给个赞
我们知道如何通过ViewPager
来展示多页面,但从 support library24.2.0推出后,你可以通过SnapHelper
这个类轻松给RecyclerView
加上类似ViewPager
的效果,这篇文章是告诉你如何给你的RecyclerView
添加page indicators
,如果你有阅读过我的博客的话,你可能知道我接下来会怎么做:
Pager 初始化
第一步,初始化你的RecyclerView
,确保你的itemlayout
设置成了layout_width="match_parent"
,否则的话没那么容易一页一页滚动。你的RecyclerView
高度也应该设置成match_parent
,如果是设置成wrap_content
的话要确保你的items
也要有相同的高度。
把PagerSnapHelper
添加到你的RecyclerView
// 给recyclerview添加background color
recyclerView.setBackgroundColor(backgroundColor);
MyAdapter adapter = ...
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(context,
LinearLayoutManager.HORIZONTAL, false));
// 添加PagerSnapHelper
PagerSnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
我们现在有了个简单的可以翻页滚动的RecyclerView
,当我们添加一个background color
在这里的话,在底部画白色的decorations
就会比较好看。
添加Pager Indicator
提示:如果你对于decorations
没有任何了解的话你可以通过 introduction to decorations 入门我是如何简单在items
之间画一个下划线Indicator
。
下一步我们需要添加decoration
来画indicator
,我们创建一个LinePagerIndicatorDecoration
并把它添加到RecyclerView
// pager indicator
recyclerView.addItemDecoration(newLinePagerIndicatorDecoration());
我们的decoration
要关注2个方法
getItemOffsets
:给每个ItemView添加padding,不会出现overlaying
onDrawOver
:在上层画decoration
,绘制的内容在itemview上层。
我喜欢用getItemOffsets
方法来确保我的items
没有overdraw
,如果你的indicator
倾向overdraw
,你可以忽略这个getItemOffsets
方法。我们做
的一切是需要indicatorHeight
的偏移在每个View
的底部。如果你使用
GridLayoutManager
,你需要确保你的items
仅仅只是在底部偏移了
@Override
public void getItemOffsets(Rect outRect, View view,
RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = indicatorHeight;
}
这个在底部的偏移也是我为什么设置了一个RecyclerView
的background
而不是pages
上面, 这个偏移让我们的decoration
在content
留有一点距离,所以设置一个background color
在pages
的item
上面的话会没有效果。如果你不偏移你的items
和 overlay
他们的话,你也就不需要给RecyclerView
设置background
。
接下来我们把indicators
画给我们的pages
。我们把indicator
置于RecyclerView
的底部中间,并且给每个item
画直线,每个直线之间有一些padding
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = parent.getAdapter().getItemCount();
//水平居中, 计算宽度减去距离中间的一半
float totalLength = mIndicatorItemLength * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * mIndicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2F;
// 在剩下的space垂直居中
float indicatorPosY = parent.getHeight() - mIndicatorHeight / 2F;
drawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
}
private void drawInactiveIndicators(Canvas c, float indicatorStartX,
float indicatorPosY, int itemCount) {
mPaint.setColor(colorInactive);
// item indicator的宽度包含padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float start = indicatorStartX;
for (int i = 0; i < itemCount; i++) {
// 给每个item画下划线
c.drawLine(start, indicatorPosY,
start + mIndicatorItemLength, indicatorPosY, mPaint);
start += itemWidth;
}
}
这个地方给了我们机会给每个item
画一个标记,但是这些标记在page
是选中后的时候还不是高亮的。接下来我们计算我们滚动了多远来实现一个水平滚动的动画并且把高亮的indicator
画出来。
我们通过LayoutManager
找出当前活动的page
,然后计算滑动距离的百分比。这个计算方法在你的Views
宽度设置成了match_parent
的时候会很有效简单,否则的话可能会有不确定的情况。为了改善体验我使用了AccelerateDecelerateInterpolator
来处理这个得到的百分比progress
的值,让它看起来更加自然。
//找到活动的page,它这时候的下划线应该是高亮的
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
int activePosition = layoutManager.findFirstVisibleItemPosition();
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// 找到活动的page偏移的距离 (如果用户滑动了)
final View activeChild = layoutManager.findViewByPosition(activePosition);
int left = activeChild.getLeft();
int width = activeChild.getWidth();
// 滑动时候活动的item位置在[-width, 0]
// 滑动时候加入平滑动画
float progress = mInterpolator.getInterpolation(left * -1 / (float) width);
通过这个百分比progress
我们就可以画这个高亮的indicator
,它代表着用户滚动到了哪个page
.我们使用这个百分比progress
来画这个局部高亮选中的indicator
它代表我们滚动到了哪个page
。
public void onDrawOver(Canvas c, RecyclerView parent,
RecyclerView.State state) {
super.onDrawOver(c, parent, state);
// 画正常状态下的下划线
// ...计算百分比 ...
// 画高亮状态下的下划线
drawHighlights(c, indicatorStartX, indicatorPosY, activePosition, progress, itemCount);
}
private void drawHighlights(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition, float progress, int itemCount) {
mPaint.setColor(colorActive);
// 每个item的下划线是包括padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
if (progress == 0F) {
//百分比为0没有滑动的话第一个画高亮下划线
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
c.drawLine(highlightStart, indicatorPosY,
highlightStart + mIndicatorItemLength, indicatorPosY, mPaint);
} else {
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
// 计算局部高亮下划线的长度
float partialLength = mIndicatorItemLength * progress;
// 画断开的下划线
c.drawLine(highlightStart + partialLength, indicatorPosY,
highlightStart + mIndicatorItemLength, indicatorPosY, mPaint);
// 画高亮的下划线 覆盖在下一个item
if (highlightPosition < itemCount - 1) {
highlightStart += itemWidth;
c.drawLine(highlightStart, indicatorPosY,
highlightStart + partialLength, indicatorPosY, mPaint);
}}
}
通过上面所有步骤,我们达到了预期的indicator
指示器,在RecyclerView
正确的实现page
效果
所有完整的代码可以在这里找到
还有什么要做的?
你可能发现了,我选择线条来代替圆圈做indicator
指示器,但是画圆圈通过动画来设置他们的alpha
也是可以轻松实现类似的效果的。通过使用类似的方法你可以在decorations
做很多事,你不需要修改代码就可以拿来重复使用。
这个解决方案只是一个尝试,可能还有一些潜在的错误。正如文中提到的,确定这个progress
的方法在不同宽度的时候可能就不准确,更好的方法可能是需要在SnapHelper
内部去做处理。如果你选择使用这个在你的APP的话要确保有足够的测试。