package mcxtzhang.recyclerviewdemo; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; /** * 介绍: * 作者:zhangxutong * 邮箱:mcxtzhang@163.com * CSDN:http://blog.csdn.net/zxt0601 * 时间: 16/10/18. */ public class CstLM extends RecyclerView.LayoutManager { private static final int DEFAULT_COUNT = 1; private static final int DIRECTION_NONE = -1; private static final int DIRECTION_START = 0; private static final int DIRECTION_END = 1; private static final int DIRECTION_UP = 2; private static final int DIRECTION_DOWN = 3; /* First (top-left) position visible at any point */ private int mFirstVisiblePosition; /* Consistent size applied to all child views */ private int mDecoratedChildWidth; private int mDecoratedChildHeight; /* Number of columns that exist in the grid */ private int mTotalColumnCount = DEFAULT_COUNT; /* Metrics for the visible window of our data */ private int mVisibleColumnCount; private int mVisibleRowCount; @Override public RecyclerView.LayoutParams generateDefaultLayoutParams() { return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); } @Override public void onLayoutChildren(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);//轻量回收所有View return; } if (getChildCount() == 0 && state.isPreLayout()) { //Nothing to do during prelayout when empty return; } //Scrap measure one child View scrap = recycler.getViewForPosition(0);//获取postion的View addView(scrap); measureChildWithMargins(scrap, 0, 0);//测量View /* * 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);//获取+上Decorated的 宽 高、上下 左右 mDecoratedChildHeight = getDecoratedMeasuredHeight(scrap); detachAndScrapView(scrap, recycler);//轻量回收指定View //Always update the visible row/column counts updateWindowSizing(); int childLeft; int childTop; /* * Reset the visible and scroll positions */ mFirstVisiblePosition = 0; childLeft = childTop = 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, state); } @Override public boolean canScrollHorizontally() { return true; } @Override public boolean canScrollVertically() { return true; } @Override public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { Log.d("TAG", "scrollVerticallyBy() called with: dy = [" + dy + "]"); if (getChildCount() == 0) { return 0; } //Take top measurements from the top-left child final View topView = getChildAt(0); //Take bottom measurements from the bottom-right child. final View bottomView = getChildAt(getChildCount() - 1); //Optimize the case where the entire data set is too small to scroll int viewSpan = getDecoratedBottom(bottomView) - getDecoratedTop(topView); if (viewSpan <= getVerticalSpace()) {//不足一屏幕不滑动 //We cannot scroll in either direction return 0; } int delta;//位移 int maxRowCount = getTotalRowCount();//所有Item的行数,这里应该是20 boolean topBoundReached = getFirstVisibleRow() == 0;//边界处理 boolean bottomBoundReached = getLastVisibleRow() >= maxRowCount; if (dy > 0) { // Contents are scrolling up //Check against bottom bound if (bottomBoundReached) { //If we've reached the last row, enforce limits int bottomOffset; if (rowOfIndex(getChildCount() - 1) >= (maxRowCount - 1)) { //We are truly at the bottom, determine how far bottomOffset = getVerticalSpace() - getDecoratedBottom(bottomView) + getPaddingBottom(); } else { /* * Extra space added to account for allowing bottom space in the grid. * This occurs when the overlap in the last row is not large enough to * ensure that at least one element in that row isn't fully recycled. */ bottomOffset = getVerticalSpace() - (getDecoratedBottom(bottomView) + getDecoratedMeasuredHeight(bottomView)) + getPaddingBottom(); } delta = Math.max(-dy, bottomOffset); } else { //No limits while the last row isn't visible delta = -dy; } } else { // Contents are scrolling down //Check against top bound if (topBoundReached) { int topOffset = -getDecoratedTop(topView) + getPaddingTop(); delta = Math.min(-dy, topOffset);//下拉,dy 本来是负数,取- ,正数,所以 } else { delta = -dy; } } offsetChildrenVertical(delta);//这里是先平移,再填充 if (dy > 0) { if (getDecoratedBottom(topView) < 0 && !bottomBoundReached) {//第一个View移出屏幕 且没到底部 可见要+1 //mFirstVisiblePosition++; fillGrid(DIRECTION_DOWN, recycler, state); } else if (!bottomBoundReached) { fillGrid(DIRECTION_NONE, recycler, state); } } else { if (getDecoratedTop(topView) > 0 && !topBoundReached) {//第一个View 离顶部有距离了,且没到顶部 可见要-1 //mFirstVisiblePosition--; fillGrid(DIRECTION_UP, recycler, state); } else if (!topBoundReached) { fillGrid(DIRECTION_NONE, recycler, state); } } return -delta; } private void fillGrid(int direction, RecyclerView.Recycler recycler, RecyclerView.State state) { fillGrid(direction, 0, 0, recycler, state); } private void fillGrid(int direction, int emptyLeft, int emptyTop, RecyclerView.Recycler recycler, RecyclerView.State state) { if (mFirstVisiblePosition < 0) mFirstVisiblePosition = 0;//边界处理 if (mFirstVisiblePosition >= getItemCount()) mFirstVisiblePosition = (getItemCount() - 1); //1 清点目前我们所有的视图。将他们 Detach 以便稍后重新连接。(主要还是for scroll,平移后 view移动了 ) SparseArray<View> viewCache = new SparseArray<View>(getChildCount()); int startLeftOffset = emptyLeft; int startTopOffset = emptyTop; if (getChildCount() != 0) { final View topView = getChildAt(0); startLeftOffset = getDecoratedLeft(topView); startTopOffset = getDecoratedTop(topView); switch (direction) { /* case DIRECTION_START: startLeftOffset -= mDecoratedChildWidth; break; case DIRECTION_END: startLeftOffset += mDecoratedChildWidth; break;*/ case DIRECTION_UP: startTopOffset -= getDecoratedMeasuredHeight(getChildAt(getChildCount() - 1)); break; case DIRECTION_DOWN: startTopOffset += getDecoratedMeasuredHeight(topView); break; } //Cache all views by their existing position, before updating counts for (int i = 0; i < getChildCount(); i++) { int position = positionOfIndex(i); final View child = getChildAt(i); viewCache.put(position, child); } //Temporarily detach all views. // Views we still need will be added back at the proper index. for (int i = 0; i < viewCache.size(); i++) { detachView(viewCache.valueAt(i)); } } /* * Next, we advance the visible position based on the fill direction. * DIRECTION_NONE doesn't advance the position in any direction. */ switch (direction) { case DIRECTION_START: mFirstVisiblePosition--; break; case DIRECTION_END: mFirstVisiblePosition++; break; case DIRECTION_UP: mFirstVisiblePosition -= getTotalColumnCount(); break; case DIRECTION_DOWN: mFirstVisiblePosition += getTotalColumnCount(); break; } //2 测量/布局每一个当前可见的子视图。重新连接已有的视图很简单; 新的视图是从 Recycler 之中获取的。 int leftOffset = startLeftOffset; int topOffset = startTopOffset; int nextPosition = 0; for (int i = 0; i < getVisibleChildCount(); i++) { nextPosition = positionOfIndex(i); //... //Layout this position View view = viewCache.get(nextPosition); if (view == null) { /* * The Recycler will give us either a newly constructed view, * or a recycled view it has on-hand. In either case, the * view will already be fully bound to the data by the * adapter for us. */ view = recycler.getViewForPosition(nextPosition); addView(view); /* * It is prudent to measure/layout each new view we * receive from the Recycler. We don't have to do * this for views we are just re-arranging. */ measureChildWithMargins(view, 0, 0); layoutDecorated(view, leftOffset, topOffset, leftOffset + getDecoratedMeasuredWidth(view), topOffset + getDecoratedMeasuredHeight(view)); } else { //Re-attach the cached view at its new index attachView(view);//将detach的View add回来 viewCache.remove(nextPosition); } if (i % mVisibleColumnCount == (mVisibleColumnCount - 1)) {//换行 leftOffset = startLeftOffset; topOffset += getDecoratedMeasuredHeight(view); } else {//增加左边距 leftOffset += getDecoratedMeasuredWidth(view); } } /* //add by zhangxutong Feature1: 不同大小的Item也适应 add完以后看看是否没填满 while (getVerticalSpace() > topOffset) { View additionalView = viewCache.get(++nextPosition);//取出缓存 if (additionalView != null) { attachView(additionalView); viewCache.remove(nextPosition); } else { additionalView = recycler.getViewForPosition(nextPosition); addView(additionalView); measureChildWithMargins(additionalView, 0, 0); layoutDecoratedWithMargins(additionalView, leftOffset, topOffset, leftOffset + getDecoratedMeasuredWidth(additionalView), topOffset + getDecoratedMeasuredHeight(additionalView)); } if (nextPosition % mVisibleColumnCount == (mVisibleColumnCount - 1)) {//换行 leftOffset = startLeftOffset; topOffset += getDecoratedMeasuredHeight(additionalView); } else {//增加左边距 leftOffset += getDecoratedMeasuredWidth(additionalView); } }*/ for (int i = 0; i < viewCache.size(); i++) { recycler.recycleView(viewCache.valueAt(i));//detachView 后 没有attachView的话 就要真的回收掉他们 } } /* * Rather than continuously checking how many views we can fit * based on scroll offsets, we simplify the math by computing the * visible grid as what will initially fit on screen, plus one. */ // 计算出 一个屏幕的行列数 private void updateWindowSizing() { //求出可见的能容纳多少列 mVisibleColumnCount = (getHorizontalSpace() / mDecoratedChildWidth) + 1; if (getHorizontalSpace() % mDecoratedChildWidth > 0) {//不整除 多加一列 mVisibleColumnCount++; } //Allow minimum value for small data sets if (mVisibleColumnCount > getTotalColumnCount()) { mVisibleColumnCount = getTotalColumnCount();//边界处理 不能大于一共的ItemCount } //求出可见的能容纳多少行 mVisibleRowCount = (getVerticalSpace() / mDecoratedChildHeight) + 1; if (getVerticalSpace() % mDecoratedChildHeight > 0) { mVisibleRowCount++; } if (mVisibleRowCount > getTotalRowCount()) { mVisibleRowCount = getTotalRowCount(); } } /* * Mapping between child view indices and adapter data * positions helps fill the proper views during scrolling. */ //根据index 返回当前item的postion private int positionOfIndex(int childIndex) { int row = childIndex / mVisibleColumnCount; int column = childIndex % mVisibleColumnCount; return mFirstVisiblePosition + (row * getTotalColumnCount()) + column; } private int getVisibleChildCount() { return mVisibleColumnCount * mVisibleRowCount; } /** * 第几行 * * @param childIndex * @return */ private int rowOfIndex(int childIndex) { int position = positionOfIndex(childIndex); return position / getTotalColumnCount(); } /** * 可见的第一行,是第几行 * * @return */ private int getFirstVisibleRow() { return (mFirstVisiblePosition / getTotalColumnCount()); } /** * 可见的最后一行,是第几行 * * @return */ private int getLastVisibleRow() { return getFirstVisibleRow() + mVisibleRowCount; } /** * 定义的列数 * * @return */ private int getTotalColumnCount() { if (getItemCount() < mTotalColumnCount) {//一共的item 都不够列数,那么就返回itemCount return getItemCount(); } return mTotalColumnCount; } /** * 一共的行数 * * @return */ private int getTotalRowCount() { if (getItemCount() == 0 || mTotalColumnCount == 0) {//异常情况处理 return 0; } int maxRow = getItemCount() / mTotalColumnCount;//最大行数 //Bump the row count if it's not exactly even if (getItemCount() % mTotalColumnCount != 0) {//不能整除要+1 maxRow++; } return maxRow; } private int getHorizontalSpace() { return getWidth() - getPaddingRight() - getPaddingLeft(); } private int getVerticalSpace() { return getHeight() - getPaddingBottom() - getPaddingTop(); } }