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
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) /
2
F
// 在剩下的space垂直居中
float
indicatorPosY
= parent.getHeight() - mIndicatorHeight /
2
F
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
// 给每个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);
final
float
itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
if (progress ==
0
F) {
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);
if (highlightPosition < itemCount -
1
) {
highlightStart += itemWidth;
c
.drawLine
(highlightStart, indicatorPosY,
highlightStart + partialLength, indicatorPosY, mPaint);
复制代码
通过上面所有步骤,我们达到了预期的
indicator
指示器,在
RecyclerView
正确的实现
page
效果
所有完整的代码可以在
这里找到
还有什么要做的?
你可能发现了,我选择线条来代替圆圈做
indicator
指示器,但是画圆圈通过动画来设置他们的
alpha
也是可以轻松实现类似的效果的。通过使用类似的方法你可以在
decorations
做很多事,你不需要修改代码就可以拿来重复使用。
这个解决方案只是一个尝试,可能还有一些潜在的错误。正如文中提到的,确定这个
progress
的方法在不同宽度的时候可能就不准确,更好的方法可能是需要在
SnapHelper
内部去做处理。如果你选择使用这个在你的APP的话要确保有足够的测试。