快速实现自定义LayoutManager

1,000 阅读2分钟

快速上手之先把套路搞起来 :

1、继承RecyclerView.LayoutManager

public class LinearLayoutManager extends RecyclerView.LayoutManager implements
        ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
}

2、实现generateDefaultLayoutParams

@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
    return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}

3、重写isAutoMeasureEnabled或者onMeasure方法

@Override
public boolean isAutoMeasureEnabled() {
    return true;
}
//isAutoMeasureEnabled和onMeasure方法互斥 根据实际情况重写其中一个
//RecyclerView自带的三个LayoutManager均实现了isAutoMeasureEnabled
@Override
public void onMeasure(@NonNull RecyclerView.Recycler recycler, 
@NonNull RecyclerView.State state, int widthSpec, int heightSpec) {
    super.onMeasure(recycler, state, widthSpec, heightSpec);
}

4、重写onLayoutChildren 开始填充子VIew(注意此处不可一次性填充所有view,只填充屏幕可 展示view即可)

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    super.onLayoutChildren(recycler, state);
}

5、重写canScrollVerticall或者canScrollHorizontally来决定滑动方向(两个都写表示支持横竖滑动)

@Override
public boolean canScrollVertically() {
    return true;
}

6、重写scrollVerticallyBy或者scrollHorizontallyBy 在滑动的时候做边界处理,item的回收以及重用逻辑

@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    //这里编写逻辑 realDy 表示经过计算之后的需要滑动的真实距离
    int realDy = dy;
    ...
    return realDy ;
}

7、scrollToPosition()和smoothScrollToPosition()方法支持

直接看LinearLayoutManager.scrollToPosition代码实现:

@Override
public void scrollToPosition(int position) {
    mPendingScrollPosition = position;
    mPendingScrollPositionOffset = INVALID_OFFSET;
    if (mPendingSavedState != null) {
        mPendingSavedState.invalidateAnchor();
    }
    requestLayout();
}

7.1 、将mPendingScrollPosition设置为需要滚动到的position,然后调用requestLayout()方法,这个方法会回调onLayoutChildren,在onLayoutChildren中会调用updateAnchorFromPendingData()根据position获取view看此view是否正处于屏幕内,然后再根据这个position判断这个view是从左往右布局,还是从右往左布局

private boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
    ...
    if (mPendingScrollPositionOffset == INVALID_OFFSET) {
        //根据position获取需要滚动到的view
        View child = findViewByPosition(mPendingScrollPosition);
        //如果view处于屏幕内(有可能未完全显示)
        if (child != null) {
            ...
            //获取view的左边缘
            final int startGap = mOrientationHelper.getDecoratedStart(child)
                    - mOrientationHelper.getStartAfterPadding();
            //如果小于0则表示未完全显示 左边缘处于屏幕外
            if (startGap < 0) {
                anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding();
               //从左到右布局
                 anchorInfo.mLayoutFromEnd = false;
                return true;
            }
            //使用layoutManager的宽度减去view的右边缘
            final int endGap = mOrientationHelper.getEndAfterPadding()
                    - mOrientationHelper.getDecoratedEnd(child);
            //如果结果小于0则表示右边缘处于屏幕外        
            if (endGap < 0) {
                anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding();
                 //从右到左布局
                anchorInfo.mLayoutFromEnd = true;
                return true;
            }
           ...
        } else { // item is not visible.
        //如果根据position获取需要滚动到的view不可见
            if (getChildCount() > 0) {
                // get position of any child, does not matter
                //获取当前屏幕上第一个显示的view的position
                int pos = getPosition(getChildAt(0));
                //通过对比当前需要滚动到的view的position以及当前显示的view的position来决定如何布局
                anchorInfo.mLayoutFromEnd = mPendingScrollPosition < pos
                        == mShouldReverseLayout;
            }
              ...
        }
        return true;

8、适配smoothScrollToPosition()

//LinearLayuotManager
    @Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
        int position) {
    LinearSmoothScroller linearSmoothScroller =
            new LinearSmoothScroller(recyclerView.getContext());
    linearSmoothScroller.setTargetPosition(position);
    startSmoothScroll(linearSmoothScroller);
}

8.1自定义的layoutManager实现ScrollVectorProvider接口

public class LinearLayoutManager extends RecyclerView.LayoutManager implements
        ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
}

8.2 重写computeScrollVectorForPosition方法

@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
    if (getChildCount() == 0) {
        return null;
    }
    final int firstChildPos = getPosition(getChildAt(0));
    final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1;
    if (mOrientation == HORIZONTAL) {
        return new PointF(direction, 0);
    } else {
        return new PointF(0, direction);
    }
}

9、回收View:如果能在第一次布局和滑动的时候准确的做好Holder的回收以及复用的话,则可以直 接拿到mAttachedScrap中的holder直接放到mRecyclerPool中回收。(例如每次滑动的时候都有调用detachAndScrapAttachedViews,然后在再去缓存中拿去有用的itemview)

//拿到临时缓存
List scrapList = recycler.getScrapList();
//遍历,然后先移除,后回收,其实也就是removeAndRecycleView方法所做的事
for (int i = 0; i < scrapList.size(); i++) {
    RecyclerView.ViewHolder holder = scrapList.get(i);
    removeView(holder.itemView);
    recycler.recycleView(holder.itemView);
 }

否则需要自己计算开始以及结束index去回收

private void recycleChildren(RecyclerView.Recycler recycler, 
 int startIndex, int endIndex) {
        if (startIndex == endIndex) {
            return;
        }
        if (endIndex > startIndex) {
            for (int i = endIndex - 1; i >= startIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
        } else {
            for (int i = startIndex; i > endIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
        }
    }

以上为自定义Layoutmananger所需步骤,注意以上并非完整代码,实际仍然在各个步骤中补充业务逻辑,需要根据实际业务做到完整的item回收,复用,滑动距离计算,滑动边界计算才能真正实现完整的Layoutmanager