After understanding, you can basically master the custom LayoutManager

Function description:

1: Support caching;

2: Solve the problem of item dragging;

3: Custom Jiugongge pagination problem.

/**
 * @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;
        }
    }
    }

style:

Guess you like

Origin blog.csdn.net/CHNE_TAO_EMSM/article/details/132142997