前記
我上篇文章 Android Support Library 24.2.0 更新介紹 簡單介紹了 LinearSnapHelper奈梳,有點類似 ViewPager,比如可以用于橫向瀏覽圖片圣勒,LinearSnapHelper 保證了每次滾動完自動滑動到中心的 Item.
正題
前幾天項目有個需求费变,看了一下剛好可以用上 LinearSnapHelper,用起來非常簡單
SnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
實際運行效果如圖
因為有縱橫向的滾動圣贸,所以用了 RecyclerView 的嵌套挚歧,目前看運行良好。
但是實際體驗發(fā)現(xiàn)有個問題吁峻,有時候在手指在內(nèi)層的 RecyclerView 上下滾動時滑负,觸摸事件被吃了,沒有反應用含,按道理講矮慕,橫向滾動的 RecyclerView 應該不攔截這種事件才對。而且這個現(xiàn)象并不是必現(xiàn)啄骇,發(fā)現(xiàn)如果是橫向滾動到第二個 Item 在縱向滾 就沒有問題痴鳄,只有第一個 Item (其實還有最后一個 Item)會出現(xiàn)這種現(xiàn)象,簡單講 出現(xiàn)的步驟大概是這樣的缸夹,先滾動到 第二個 Item 在滾回來第一個 Item 就會出現(xiàn)這個bug夏跷。
Fix 之路
首先 debug 了 內(nèi)層 RecyclerView 的 onInterceptTouchEvent哼转,發(fā)現(xiàn)這個時候 return 為 true, 而且這時 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;
因此這個時候 RecyclerView 會攔截事件壹蔓。
事實上導致 RecyclerView 的狀態(tài) 為 SCROLL_STATE_SETTLING 是因為 LinearSnapHelper。
LinearSnapHelper 原理是監(jiān)聽 RecyclerView 的滾動狀態(tài)猫态,一旦處于RecyclerView.SCROLL_STATE_IDLE (當然還有 Fling 的情況佣蓉,這里先不看) 就會計算得到要處于中心的 Item View,然后在計算需要滾動的距離亲雪。
處于中心這個點很重要勇凭,細心的讀者可能會發(fā)現(xiàn)第一個 Item 并不會處于中心的,那么這個時候 LinearSnapHelper 會怎么處理呢义辕?
LinearSnapHelper 會根據(jù) calculateDistanceToFinalSnap 得到的距離 利用 RecyclerView 的 smoothScrollBy 來滾動虾标,而這個時候第一個 Item 永遠都滾動不到中心的位置,于是不停的 ScrollBy 也就導致 狀態(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)滾動到盡頭 并且判斷是否是第一個item或者是最后一個基显,直接返回0蘸吓,不用多余的滾動了
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;
}
}