前記
我上篇文章 Android Support Library 24.2.0 更新介紹 簡(jiǎn)單介紹了 LinearSnapHelper,有點(diǎn)類似 ViewPager,比如可以用于橫向?yàn)g覽圖片,LinearSnapHelper 保證了每次滾動(dòng)完自動(dòng)滑動(dòng)到中心的 Item.
正題
前幾天項(xiàng)目有個(gè)需求,看了一下剛好可以用上 LinearSnapHelper,用起來非常簡(jiǎn)單
SnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
實(shí)際運(yùn)行效果如圖

因?yàn)橛锌v橫向的滾動(dòng),所以用了 RecyclerView 的嵌套,目前看運(yùn)行良好。
但是實(shí)際體驗(yàn)發(fā)現(xiàn)有個(gè)問題,有時(shí)候在手指在內(nèi)層的 RecyclerView 上下滾動(dòng)時(shí),觸摸事件被吃了,沒有反應(yīng),按道理講,橫向滾動(dòng)的 RecyclerView 應(yīng)該不攔截這種事件才對(duì)。而且這個(gè)現(xiàn)象并不是必現(xiàn),發(fā)現(xiàn)如果是橫向滾動(dòng)到第二個(gè) Item 在縱向滾 就沒有問題,只有第一個(gè) Item (其實(shí)還有最后一個(gè) Item)會(huì)出現(xiàn)這種現(xiàn)象,簡(jiǎn)單講 出現(xiàn)的步驟大概是這樣的,先滾動(dòng)到 第二個(gè) Item 在滾回來第一個(gè) Item 就會(huì)出現(xiàn)這個(gè)bug。
Fix 之路
首先 debug 了 內(nèi)層 RecyclerView 的 onInterceptTouchEvent,發(fā)現(xiàn)這個(gè)時(shí)候 return 為 true, 而且這時(shí) RecyclerView 的狀態(tài) 不是 SCROLL_STATE_IDLE,而是 SCROLL_STATE_SETTLING ,看 RecyclerView 的源碼
if (mScrollState == SCROLL_STATE_SETTLING) {
getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
}
...
return mScrollState == SCROLL_STATE_DRAGGING;
因此這個(gè)時(shí)候 RecyclerView 會(huì)攔截事件。
事實(shí)上導(dǎo)致 RecyclerView 的狀態(tài) 為 SCROLL_STATE_SETTLING 是因?yàn)?LinearSnapHelper。
LinearSnapHelper 原理是監(jiān)聽 RecyclerView 的滾動(dòng)狀態(tài),一旦處于RecyclerView.SCROLL_STATE_IDLE (當(dāng)然還有 Fling 的情況,這里先不看) 就會(huì)計(jì)算得到要處于中心的 Item View,然后在計(jì)算需要滾動(dòng)的距離。
處于中心這個(gè)點(diǎn)很重要,細(xì)心的讀者可能會(huì)發(fā)現(xiàn)第一個(gè) Item 并不會(huì)處于中心的,那么這個(gè)時(shí)候 LinearSnapHelper 會(huì)怎么處理呢?
LinearSnapHelper 會(huì)根據(jù) calculateDistanceToFinalSnap 得到的距離 利用 RecyclerView 的 smoothScrollBy 來滾動(dòng),而這個(gè)時(shí)候第一個(gè) Item 永遠(yuǎn)都滾動(dòng)不到中心的位置,于是不停的 ScrollBy 也就導(dǎo)致 狀態(tài) 為 SCROLL_STATE_SETTLING。
知道原因后就好辦了, 重寫 calculateDistanceToFinalSnap 修正距離就可以了
用以下的 FixLinearSnapHelper 代替 LinearSnapHelper 就可以了。
public class FixLinearSnapHelper extends LinearSnapHelper{
private OrientationHelper mVerticalHelper;
private OrientationHelper mHorizontalHelper;
private RecyclerView mRecyclerView;
@Override
public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
@NonNull View targetView) {
int[] out = new int[2];
if (layoutManager.canScrollHorizontally()) {
out[0] = distanceToCenter(targetView, getHorizontalHelper(layoutManager));
} else {
out[0] = 0;
}
if (layoutManager.canScrollVertically()) {
out[1] = distanceToCenter(targetView, getVerticalHelper(layoutManager));
} else {
out[1] = 0;
}
return out;
}
@Override
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException {
this.mRecyclerView = recyclerView;
super.attachToRecyclerView(recyclerView);
}
private int distanceToCenter(View targetView, OrientationHelper helper) {
//如果已經(jīng)滾動(dòng)到盡頭 并且判斷是否是第一個(gè)item或者是最后一個(gè),直接返回0,不用多余的滾動(dòng)了
if ((helper.getDecoratedStart(targetView) == 0 && mRecyclerView.getChildAdapterPosition(targetView) == 0)
|| (helper.getDecoratedEnd(targetView) == helper.getEndAfterPadding()
&& mRecyclerView.getChildAdapterPosition(targetView) == mRecyclerView.getAdapter().getItemCount() - 1) )
return 0;
int viewCenter = helper.getDecoratedStart(targetView) + (helper.getDecoratedEnd(targetView) - helper.getDecoratedStart(targetView))/2;
int correctCenter = (helper.getEndAfterPadding() - helper.getStartAfterPadding())/2;
return viewCenter - correctCenter;
}
private OrientationHelper getVerticalHelper(RecyclerView.LayoutManager layoutManager) {
if (mVerticalHelper == null) {
mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
}
return mVerticalHelper;
}
private OrientationHelper getHorizontalHelper(RecyclerView.LayoutManager layoutManager) {
if (mHorizontalHelper == null) {
mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
}
return mHorizontalHelper;
}
}