RecyclerView的position==0位置上的View高度為0時乙墙,canScrollVertically方法無法判斷是否已經(jīng)滑動到頂部颖变?
看View中 canScrollVertically
方法的調(diào)用:
/**
* Check if this view can be scrolled vertically in a certain direction.
*
* @param direction Negative to check scrolling up, positive to check scrolling down.
* @return true if this view can be scrolled in the specified direction, false otherwise.
*/
public boolean canScrollVertically(int direction) {
final int offset = computeVerticalScrollOffset();
final int range = computeVerticalScrollRange() - computeVerticalScrollExtent();
if (range == 0) return false;
if (direction < 0) {
return offset > 0;
} else {
return offset < range - 1;
}
}
這里邊主要調(diào)用了三個方法,分別是computeVerticalScrollOffset()
表示縱向滑動的距離伶丐,如果距離為0悼做,表示已經(jīng)滑動到了頂部,computeVerticalScrollRange()
指的是整體高度疯特,包括所顯示區(qū)域和屏幕區(qū)域外的高度,computeVerticalScrollExtent()
表示的是顯示區(qū)域的高度哗魂。
問題出現(xiàn)的原因
當(dāng)滑動到頂部的時候,computeVerticalScrollOffset()
返回的高度一直是大于0的數(shù)漓雅,那么canScrollVertically
就一致返回true,表示還沒有滑動到頂部录别。接下來我們RecyclerView中方法computeVerticalScrollOffset()
的實(shí)現(xiàn)。?RecyclerView這個方法的實(shí)現(xiàn)是直接調(diào)用LayoutManager的方法computeVerticalScrollOffset
邻吞,我使用的LinearLayoutManager,所以直接看這個里邊方法的實(shí)現(xiàn)组题。代碼如下:
@Override
public int computeVerticalScrollOffset(RecyclerView.State state) {
return computeScrollOffset(state);
}
這里又直接調(diào)用的computeScrollOffset
方法
private int computeScrollOffset(RecyclerView.State state) {
if (getChildCount() == 0) {
return 0;
}
ensureLayoutState();
return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper,
findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
this, mSmoothScrollbarEnabled, mShouldReverseLayout);
}
這里在主要看一下findFirstVisibleChildClosestToStart
方法,這個方法主要是用來獲取屏幕中第一個可見的childView抱冷,找到并返回這個childView崔列。
/**
* Convenience method to find the visible child closes to start. Caller should check if it has
* enough children.
*
* @param completelyVisible Whether child should be completely visible or not
* @return The first visible child closest to start of the layout from user's perspective.
*/
private View findFirstVisibleChildClosestToStart(boolean completelyVisible,
boolean acceptPartiallyVisible) {
if (mShouldReverseLayout) {
return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible,
acceptPartiallyVisible);
} else {
return findOneVisibleChild(0, getChildCount(), completelyVisible,
acceptPartiallyVisible);
}
}
下邊是查找第一個可見childView的詳細(xì)代碼,看完下邊的代碼就發(fā)現(xiàn)旺遮,如果第一個childView的高度為0的時候赵讯,會繼續(xù)往下查找,找到第一個不為0的childView并返回耿眉。
View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible,
boolean acceptPartiallyVisible) {
ensureLayoutState();
final int start = mOrientationHelper.getStartAfterPadding();
final int end = mOrientationHelper.getEndAfterPadding();
final int next = toIndex > fromIndex ? 1 : -1;
View partiallyVisible = null;
for (int i = fromIndex; i != toIndex; i += next) {
final View child = getChildAt(i);
final int childStart = mOrientationHelper.getDecoratedStart(child);
final int childEnd = mOrientationHelper.getDecoratedEnd(child);
//如果第一個childView高度為0边翼,childStart和childEnd都等于0;下邊的條件不成立鸣剪,會繼續(xù)往下邊尋找组底。
if (childStart < end && childEnd > start) {
if (completelyVisible) {
if (childStart >= start && childEnd <= end) {
return child;
} else if (acceptPartiallyVisible && partiallyVisible == null) {
partiallyVisible = child;
}
} else {
return child;
}
}
}
return partiallyVisible;
}
computeVerticalScrollOffset()
的最終返回值來自這里
/**
* @param startChild View closest to start of the list. (top or left)
* @param endChild View closest to end of the list (bottom or right)
*/
static int computeScrollOffset(RecyclerView.State state, OrientationHelper orientation,
View startChild, View endChild, RecyclerView.LayoutManager lm,
boolean smoothScrollbarEnabled, boolean reverseLayout) {
if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null ||
endChild == null) {
return 0;
}
//獲取第一個高度不為0的childView的position,如果RecylerView的第一個item高度為0筐骇,那么minPosition的值肯定大于0
final int minPosition = Math.min(lm.getPosition(startChild),
lm.getPosition(endChild));
final int maxPosition = Math.max(lm.getPosition(startChild),
lm.getPosition(endChild));
//reverseLayout默認(rèn)是false债鸡,所以itemBefore基本上就是等于minPosition的值
final int itemsBefore = reverseLayout
? Math.max(0, state.getItemCount() - maxPosition - 1)
: Math.max(0, minPosition);
if (!smoothScrollbarEnabled) {
return itemsBefore;
}
final int laidOutArea = Math.abs(orientation.getDecoratedEnd(endChild) -
orientation.getDecoratedStart(startChild));
final int itemRange = Math.abs(lm.getPosition(startChild) -
lm.getPosition(endChild)) + 1;
final float avgSizePerRow = (float) laidOutArea / itemRange;
//通過返回的值,如果minPosition != 0,返回值都大于0铛纬。
return Math.round(itemsBefore * avgSizePerRow + (orientation.getStartAfterPadding()
- orientation.getDecoratedStart(startChild)));
}
解決方案
要解決這個這個問題厌均,需要將第一個item的高度設(shè)置為1像素就可以啦。