原文地址 https://blog.davidmedenjak.com/android/2017/06/24/viewpager-recyclerview.htmlhtml
先貼最後的效果圖,全部完整的代碼能夠在這裏找到,以爲不錯的能夠給個贊java
咱們知道如何經過ViewPager
來展現多頁面,但從 support library24.2.0推出後,你能夠經過SnapHelper
這個類輕鬆給RecyclerView
加上相似ViewPager
的效果,這篇文章是告訴你如何給你的RecyclerView
添加page indicators
,若是你有閱讀過個人博客的話,你可能知道我接下來會怎麼作:android
第一步,初始化你的RecyclerView
,確保你的itemlayout
設置成了layout_width="match_parent"
,不然的話沒那麼容易一頁一頁滾動。你的RecyclerView
高度也應該設置成match_parent
,若是是設置成wrap_content
的話要確保你的items
也要有相同的高度。git
把PagerSnapHelper
添加到你的RecyclerView
github
// 給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
就會比較好看。bash
提示:若是你對於decorations
沒有任何瞭解的話你能夠經過 introduction to decorations 入門我是如何簡單在items
之間畫一個下劃線Indicator
。app
下一步咱們須要添加decoration
來畫indicator
,咱們建立一個LinePagerIndicatorDecoration
並把它添加到RecyclerViewide
// 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的話要確保有足夠的測試。