帖子页面用RecyclerView-FastScroll来给RecyclerView
加上快速滚动的滑块,同时,为了统一布局风格,标题用了CollapsingToolbarLayout
,和RecyclerView
有嵌套滑动。需要在关联滚动的同时保持滑块的位置,轮子并没有考虑到这个问题,那么魔改开始。
先来看效果:
画个示意图:
首先,通过两个回调函数获取关联滚动的高度和关联滚动的距离
1 2
| var nestedScrollRange = { 0 } var nestedScrollDistance = { 0 }
|
把关联滚动高度看作一个额外的子项,有:
在FastScrollRecyclerView.onUpdateScrollbar
里计算当前滑块位置:
由于多了嵌套滑动的距离,touchFraction
就不能由FastScroller
算出来,直接传滑块位置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| if (mIsDragging) { if (mLastY == 0 || Math.abs(mLastY - y) >= mTouchSlop) { mLastY = y; // Update the fastscroller section name at this touch position - boolean layoutManagerReversed = mRecyclerView.isLayoutManagerReversed(); - int bottom = mRecyclerView.getHeight() - mThumbHeight; - float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffset)); - - // Represents the amount the thumb has scrolled divided by its total scroll range - float touchFraction = boundedY / bottom; - if (layoutManagerReversed) { - touchFraction = 1 - touchFraction; - } - - String sectionName = mRecyclerView.scrollToPositionAtProgress(touchFraction); + String sectionName = mRecyclerView.scrollToPositionAtProgress(y - mTouchOffset); mPopup.setSectionName(sectionName); mPopup.animateVisibility(!sectionName.isEmpty()); mRecyclerView.invalidate(mPopup.updateFastScrollerBounds(mRecyclerView, mThumbPosition.y)); } }
|
在FastScrollRecyclerView.scrollToPositionAtProgress
里与当前滑块位置相减得滚动位移:
然后模拟关联滚动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) val consumed = IntArray(2) val offsetInWindow = IntArray(2) dispatchNestedPreScroll(0, dy.roundToInt(), consumed, offsetInWindow) dy -= consumed[1].toFloat() val scrollY = Math.min(Math.max(scrolledPastHeight + dy, 0f), availableScrollHeight.toFloat()) dy -= scrollY - scrolledPastHeight dispatchNestedScroll( consumed[0], consumed[1], 0, Math.max(-nestedDistance + consumed[1], dy.roundToInt()), offsetInWindow )
|
这里用scrollY
先预测了RecyclerView
所能消耗的滚动距离,再从高度缓存中找到对应的子项,调用LayoutManager.scrollToPositionWithOffset
滚到对应位置,并返回其标题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| val layoutManager = layoutManager val adapter = adapter var totalOffset = 0 itemHeightCache.forEachIndexed { index, height -> if (scrollY >= totalOffset && scrollY <= totalOffset + height) { val wrapIndex = getAdapterItemIndex(index) if (layoutManager is LinearLayoutManager) layoutManager.scrollToPositionWithOffset(wrapIndex, totalOffset - scrollY.roundToInt()) else if (adapter is MeasurableAdapter && layoutManager is StaggeredGridLayoutManager) layoutManager.scrollToPositionWithOffset(wrapIndex, totalOffset - scrollY.roundToInt()) val sectionedAdapter = (adapter as? SectionedAdapter) ?: return "" return sectionedAdapter.getSectionName(wrapIndex) } totalOffset += height } return ""
|
完整代码传送门