功能描述:
1:支持缓存;
2:解决item拖动问题;
3:自定义九宫格分页问题。
/**
* @Author chentao 0000668668
* @Time 2022/12/23
* @Description 自定义 实现水平垂直滚动LayoutManager 带缓存
* <p>
*/
public class LinearLayoutManagerX extends RecyclerView.LayoutManager {
@RecyclerView.Orientation
protected int mOrientation;
protected int mOffsetXY; // XY轴偏移量
protected int mVisibleTotalLength; // 可见总长度
protected int mItemTotalLength; // item总长度
protected int mItemWH; // item长度
public LinearLayoutManagerX(int orientation) {
mOrientation = orientation;
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
public boolean canScrollHorizontally() {
return mOrientation == HORIZONTAL;
}
@Override
public boolean canScrollVertically() {
return mOrientation == VERTICAL;
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
int itemCount = state.getItemCount();
if (itemCount == 0) {
removeAndRecycleAllViews(recycler);
return;
}
detachAndScrapAttachedViews(recycler);
fill("onLayoutChildren", recycler, state.getItemCount(), mOffsetXY);
}
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (mOrientation == VERTICAL) {
return 0;
}
return scrollBy(dx, recycler, state, mVisibleTotalLength, mItemTotalLength);
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state, mVisibleTotalLength, mItemTotalLength);
}
protected int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state, int visibleTotalLength, int itemTotalLength) {
detachAndScrapAttachedViews(recycler);
float lastOffset = mOffsetXY;
// 更新offset
mOffsetXY += dy;
// item总长度相对于路径总长度多出来的部分
int overflowLength = itemTotalLength - visibleTotalLength;
if (mOffsetXY < 0) {
// 避免第一个item脱离顶部向下滚动
mOffsetXY = 0;
} else if (mOffsetXY > overflowLength) {//滑动到底部,并且最后一个item即将脱离底部时
// 如果列表能滚动的话,则直接设置为可滑动的最大距离,避免最后一个item向上移
if (itemTotalLength > visibleTotalLength) {
mOffsetXY = overflowLength;
} else {
// 如果列表内容很少,不用滚动就能显示完的话,就不更新offset
// 那为什么这里是减呢?因为最上面执行了一句+=,所以现在这样做是抵消第一句的操作。
mOffsetXY -= dy;
}
}
fill("scrollBy", recycler, state.getItemCount(), mOffsetXY);
recycleChildren(recycler);
return lastOffset == mOffsetXY ? 0 : dy;
}
protected void fill(String tag, RecyclerView.Recycler recycler, int itemCount, int offsetXY) {
// 水平:itemWH=itemW=宽度 offsetXY=offsetX=水平偏移量
// 垂直:itemWH=itemH=高度 offsetXY=offsetY=垂直偏移量
int startIndex = 0;
int endIndex = itemCount;
for (int i = 0; i < itemCount; i++) {
int currentDistance = i * mItemWH - offsetXY;
if (currentDistance >= -mItemWH) {
// 判断当前距离 >= -itemWH 的即表示可见
// 得到第一个可见的position
startIndex = i;
break;
}
}
LogHelps.iLow("startIndex:" + startIndex, "endIndex:" + endIndex);
int visibleRange = 0;
for (int i = startIndex; i < endIndex; i++) {
View view = recycler.getViewForPosition(i);
addView(view);
measureChild(view, 0, 0);
mItemWH = getDecoratedMeasuredWidth(view);
int height = getDecoratedMeasuredHeight(view);
int currentDistance;
switch (mOrientation) {
case VERTICAL:
int left = (getWidth() - mItemWH) / 2;
currentDistance = i * height - offsetXY;
layoutDecorated(view, left, currentDistance, getWidth() - left, currentDistance + height);
visibleRange += height;
break;
case HORIZONTAL:
int top = (getHeight() - height) / 2;
currentDistance = i * mItemWH - offsetXY;
layoutDecorated(view, currentDistance, top, currentDistance + mItemWH, getHeight() - top);
visibleRange += mItemWH;
break;
}
if (visibleRange >= (mOrientation == VERTICAL ? getHeight() : getWidth())) {
break;
}
}
mVisibleTotalLength = (mOrientation == VERTICAL ? getHeight() : getWidth());
mItemTotalLength = mItemWH * itemCount;
}
protected void recycleChildren(RecyclerView.Recycler recycler) {
List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
LogHelps.iLow("scrapList=" + scrapList.size());
for (int i = 0; i < scrapList.size(); i++) {
RecyclerView.ViewHolder holder = scrapList.get(i);
removeView(holder.itemView);
recycler.recycleView(holder.itemView);
}
}
/**
* 高级自定义 page宫格方式 类似viewPage加载数据
*/
public static class PageGrid extends LinearLayoutManagerX {
protected int mRows; // 行
protected int mColumns; // 列
protected int mOnePageTotalCount; // 一页的总数量
protected int mPageTotalCount; // 总页数
public PageGrid(int orientation) {
this(orientation, 3, 3);
}
public PageGrid(int orientation, int rows, int columns) {
super(orientation);
this.mRows = rows;
this.mColumns = columns;
}
// 水平:itemWH=itemW=宽度 offsetXY=offsetX=水平偏移量
// 垂直:itemWH=itemH=高度 offsetXY=offsetY=垂直偏移量
@Override
protected void fill(String tag, RecyclerView.Recycler recycler, int itemCount, int offsetXY) {
calculateItemWH(itemCount);
int row = 0;
int columns = 0;
int pagePosition = 0;
int startIndex = (offsetXY / getWidth() + 0) * mOnePageTotalCount;
// 预计加载一页
int endIndex = (startIndex + mOnePageTotalCount * 2);
if (pagePosition <= 0) {
pagePosition = 0;
}
if (startIndex <= 0) {
startIndex = 0;
}
if (endIndex >= getItemCount()) {
endIndex = getItemCount();
}
LogHelps.iLow("mItemWH:" + mItemWH,
"mOnePageTotalCount:" + mOnePageTotalCount,
"mPageTotalCount" + mPageTotalCount,
"startIndex:" + startIndex,
"endIndex:" + endIndex);
// 0 - 16
// 8 - 32
for (int i = startIndex; i < endIndex; i++) {
View view = recycler.getViewForPosition(i);
addView(view);
measureChild(view, 0, 0);
// int width = getDecoratedMeasuredWidth(view);
int height = getDecoratedMeasuredHeight(view);
int left, top, right, bottom;
if (i != startIndex && (i % mColumns == 0)) { // 换行
row++;
columns = 0;
}
if (row / (mRows) == 1) { // 超出行数换下一页
row = 0;
pagePosition++;
}
// 偏移量计算:当前页+预加载页 乘以适配器宽度 + 列数*item宽度 - 偏移量
left = (offsetXY / getWidth() + pagePosition) * getWidth() + columns * mItemWH - mOffsetXY;
top = row * height;
right = left + mItemWH;
bottom = top + height;
layoutDecorated(view, left, top, right, bottom);
LogHelps.iLow(tag + "i" + i, "pagePosition:" + pagePosition, "left" + left, "row" + row, "columns" + columns);
columns++;
}
}
private void calculateItemWH(int itemCount) {
mOnePageTotalCount = mRows * mColumns;
mPageTotalCount = itemCount / mOnePageTotalCount + (itemCount % mOnePageTotalCount == 0 ? 0 : 1);
mVisibleTotalLength = (mOrientation == VERTICAL ? getHeight() : getWidth());
mItemTotalLength = mPageTotalCount * mVisibleTotalLength;
mItemWH = mVisibleTotalLength / mColumns;
}
}
/**
* 更好的支持分页滚动-也是第二种方式实现滚动
*/
public static class PageGrid2 extends PageGrid {
public PageGrid2(int orientation) {
this(orientation, 3, 3);
}
public PageGrid2(int orientation, int rows, int columns) {
super(orientation);
this.mRows = rows;
this.mColumns = columns;
}
@Override
protected int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state,
int visibleTotalLength, int itemTotalLength) {
int mTotalWidth = (mPageTotalCount - 1) * getWidth();
int newX = mOffsetXY + dy;
int result = dy;
if (newX > mTotalWidth) {
// 最底部
result = mTotalWidth - mOffsetXY;
} else if (newX < 0) {
// 最顶部
result = 0 - mOffsetXY;
}
mOffsetXY += result;
offsetChildrenHorizontal(-result);
LogHelps.iLow("mOffsetXY=" + mOffsetXY, "newX=" + newX, "result=" + result, "dy=" + dy);
detachAndScrapAttachedViews(recycler);
fill("scrollBy", recycler, state.getItemCount(), mOffsetXY);
recycleChildren(recycler);
return result;
}
}
}
样式: