原文地址 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的话要确保有足够的测试。