@Override publicvoidonLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state){ //We have nothing to show for an empty data set but clear any existing views if (getItemCount() == 0) { detachAndScrapAttachedViews(recycler); return; }
/* * We make some assumptions in this code based on every child * view being the same size (i.e. a uniform grid). This allows * us to compute the following values up front because they * won't change. */ mDecoratedChildWidth = getDecoratedMeasuredWidth(scrap); mDecoratedChildHeight = getDecoratedMeasuredHeight(scrap);
detachAndScrapView(scrap, recycler); }
updateWindowSizing();
int childLeft; int childTop; if (getChildCount() == 0) { //First or empty layout /* * Reset the visible and scroll positions */ mFirstVisiblePosition = 0; childLeft = childTop = 0; } elseif (getVisibleChildCount() > getItemCount()) { //Data set is too small to scroll fully, just reset position mFirstVisiblePosition = 0; childLeft = childTop = 0; } else { //Adapter data set changes /* * Keep the existing initial position, and save off * the current scrolled offset. */ final View topChild = getChildAt(0); if (mForceClearOffsets) { childLeft = childTop = 0; mForceClearOffsets = false; } else { childLeft = getDecoratedLeft(topChild); childTop = getDecoratedTop(topChild); }
/* * Adjust the visible position if out of bounds in the * new layout. This occurs when the new item count in an adapter * is much smaller than it was before, and you are scrolled to * a location where no items would exist. */ int lastVisiblePosition = positionOfIndex(getVisibleChildCount() - 1); if (lastVisiblePosition >= getItemCount()) { lastVisiblePosition = (getItemCount() - 1); int lastColumn = mVisibleColumnCount - 1; int lastRow = mVisibleRowCount - 1;
//Adjust to align the last position in the bottom-right mFirstVisiblePosition = Math.max( lastVisiblePosition - lastColumn - (lastRow * getTotalColumnCount()), 0);
//Correct cases where shifting to the bottom-right overscrolls the top-left // This happens on data sets too small to scroll in a direction. if (getFirstVisibleRow() == 0) { childTop = Math.min(childTop, 0); } if (getFirstVisibleColumn() == 0) { childLeft = Math.min(childLeft, 0); } } }
//Clear all attached views into the recycle bin detachAndScrapAttachedViews(recycler);
//Fill the grid for the initial layout of views fillGrid(DIRECTION_NONE, childLeft, childTop, recycler); }
当 layout 应该立即将所给位置设为第一个可见 item 时,调用 RecyclerView 的 scrollToPosition()。 在一个 vertical list 里,item 应该放在顶部;horizontal list 中,通常放在左边。在我们的 网格布局中,被选中的 item 应该放在视图的左上角。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
@Override publicvoidscrollToPosition(int position){ if (position >= getItemCount()) { Log.e(TAG, "Cannot scroll to "+position+", item count is "+getItemCount()); return; }
//Ignore current scroll offset, snap to top-left mForceClearOffsets = true; //Set requested position as first visible mFirstVisiblePosition = position; //Trigger a new view layout requestLayout(); }
因为有一个良好的 onLayoutChildren() 实现,这里就可以简单的更新目标位置并触发一个 新的 fill 操作。
@Override publicvoidsmoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, finalint position){ if (position >= getItemCount()) { Log.e(TAG, "Cannot scroll to "+position+", item count is "+getItemCount()); return; }
/* * LinearSmoothScroller's default behavior is to scroll the contents until * the child is fully visible. It will snap to the top-left or bottom-right * of the parent depending on whether the direction of travel was positive * or negative. */ LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) { /* * LinearSmoothScroller, at a minimum, just need to know the vector * (x/y distance) to travel in order to get from the current positioning * to the target. */ @Override public PointF computeScrollVectorForPosition(int targetPosition){ finalint rowOffset = getGlobalRowOfPosition(targetPosition) - getGlobalRowOfPosition(mFirstVisiblePosition); finalint columnOffset = getGlobalColumnOfPosition(targetPosition) - getGlobalColumnOfPosition(mFirstVisiblePosition);