1. 必须重写的方法
1.1 generateDefaultLayoutParams()
java
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
作用:
- 为 RecyclerView 的子项生成默认的布局参数
- 当子项没有明确设置 LayoutParams 时使用
重写场景:
- 必须重写 - 所有自定义 LayoutManager 都必须实现
- 根据布局需求设置默认的宽高参数
- 例如:瀑布流布局可能需要特殊的宽高比例
示例配置:
java
// 全宽等高布局
return new RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
// 固定网格布局
return new RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
120); // 固定高度
1.2 onLayoutChildren(Recycler recycler, State state)
java
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// 核心布局逻辑
}
作用:
- 负责布局所有可见的子视图
- 处理视图的添加、移除和回收
- 响应数据变化和重新布局请求
重写场景:
- 必须重写 - 所有自定义 LayoutManager 都必须实现
- 实现自定义布局算法
- 处理数据更新时的布局变化
关键步骤:
java
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// 1. 分离当前视图(准备重新布局)
detachAndScrapAttachedViews(recycler);
// 2. 检查空状态
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
return;
}
// 3. 计算布局边界
int startPosition = calculateStartPosition();
int endPosition = calculateEndPosition();
// 4. 布局可见项
for (int i = startPosition; i <= endPosition; i++) {
View child = recycler.getViewForPosition(i);
addView(child);
measureChildWithMargins(child, 0, 0);
layoutDecorated(child, left, top, right, bottom);
}
// 5. 回收不可见项
recycleViewsOutOfBounds(recycler);
}
2. 滚动相关方法
2.1 canScrollHorizontally()
/ canScrollVertically()
java
@Override
public boolean canScrollHorizontally() {
return true; // 或 false
}
@Override
public boolean canScrollVertically() {
return true; // 或 false
}
作用:
- 声明 LayoutManager 是否支持水平/垂直滚动
- 影响 RecyclerView 的触摸事件处理和嵌套滚动
重写场景:
- 当布局需要支持滚动时重写
- 根据布局方向决定支持哪种滚动
- 例如:横向列表需要
canScrollHorizontally()
返回 true
2.2 scrollHorizontallyBy()
/ scrollVerticallyBy()
java
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
// 处理水平滚动
int consumed = Math.min(dx, calculateMaxScrollX());
offsetChildrenHorizontal(-consumed);
fill(recycler, state);
return consumed;
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
// 处理垂直滚动
int consumed = Math.min(dy, calculateMaxScrollY());
offsetChildrenVertical(-consumed);
fill(recycler, state);
return consumed;
}
作用:
- 处理滚动事件并返回实际消耗的滚动距离
- 更新子视图位置并处理视图的回收和添加
重写场景:
- 当布局支持滚动时必须重写
- 需要精确控制滚动行为和边界检查
- 实现视差滚动等高级效果时
关键考虑:
java
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0) return 0;
// 边界检查
int availableScroll = calculateAvailableScroll(dy);
if (availableScroll == 0) return 0;
// 实际滚动
offsetChildrenVertical(-availableScroll);
// 更新可见视图
if (availableScroll > 0) {
fillAfterScroll(recycler, state);
} else {
fillBeforeScroll(recycler, state);
}
// 回收不可见视图
recycleViewsOutOfBounds(recycler);
return availableScroll;
}
3. 测量相关方法
3.1 onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec)
java
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
super.onMeasure(recycler, state, widthSpec, heightSpec);
}
作用:
- 自定义 RecyclerView 的测量逻辑
- 根据内容动态计算尺寸
重写场景:
- 需要实现 wrap_content 效果时
- 布局尺寸依赖于内容时
- 需要特殊测量逻辑的复杂布局
示例:
java
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
int widthMode = View.MeasureSpec.getMode(widthSpec);
int heightMode = View.MeasureSpec.getMode(heightSpec);
int widthSize = View.MeasureSpec.getSize(widthSpec);
int heightSize = View.MeasureSpec.getSize(heightSpec);
if (widthMode == View.MeasureSpec.AT_MOST || heightMode == View.MeasureSpec.AT_MOST) {
// 计算内容尺寸
int contentWidth = calculateTotalWidth(recycler, state);
int contentHeight = calculateTotalHeight(recycler, state);
int measuredWidth = widthMode == View.MeasureSpec.AT_MOST ?
Math.min(widthSize, contentWidth) : widthSize;
int measuredHeight = heightMode == View.MeasureSpec.AT_MOST ?
Math.min(heightSize, contentHeight) : heightSize;
setMeasuredDimension(measuredWidth, measuredHeight);
} else {
super.onMeasure(recycler, state, widthSpec, heightSpec);
}
}
4. 位置导航方法
4.1 scrollToPosition(int position)
java
@Override
public void scrollToPosition(int position) {
mPendingScrollPosition = position;
requestLayout();
}
作用:
- 立即滚动到指定位置
- 不带动画效果
重写场景:
- 需要支持程序化滚动时
- 实现自定义滚动逻辑时
- 处理锚点定位等场景
4.2 smoothScrollToPosition(RecyclerView recyclerView, State state, int position)
java
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
@Override
protected int calculateTimeForDeceleration(int dx) {
return 300;
}
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return CustomLayoutManager.this.computeScrollVectorForPosition(targetPosition);
}
};
scroller.setTargetPosition(position);
startSmoothScroll(scroller);
}
作用:
- 平滑滚动到指定位置
- 提供流畅的滚动动画
重写场景:
- 需要自定义平滑滚动行为时
- 实现特殊的滚动曲线或时长时
- 需要与自定义动画配合时
5. 预测动画支持
5.1 supportsPredictiveItemAnimations()
java
@Override
public boolean supportsPredictiveItemAnimations() {
return true;
}
作用:
- 声明是否支持预测性项目动画
- 启用更流畅的添加/删除动画
重写场景:
- 当需要流畅的项动画时
- 布局变化频繁的应用中
- 需要实现高级动画效果时
5.2 onLayoutChildren()
中的预测动画处理
java
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (state.isPreLayout()) {
// 预布局阶段 - 为动画做准备
layoutForPredictiveAnimations(recycler, state);
} else {
// 实际布局阶段
layoutForReal(recycler, state);
}
}
6. 辅助方法
6.1 computeHorizontalScrollRange()
/ computeVerticalScrollRange()
java
@Override
public int computeVerticalScrollRange(RecyclerView.State state) {
return calculateTotalHeight();
}
@Override
public int computeVerticalScrollOffset(RecyclerView.State state) {
return getCurrentScrollY();
}
@Override
public int computeVerticalScrollExtent(RecyclerView.State state) {
return getHeight();
}
作用:
- 计算滚动条相关的数值
- 影响滚动条的显示和行为
重写场景:
- 需要自定义滚动条行为时
- 实现视差滚动等复杂效果时
- 滚动范围与内容尺寸不匹配时
6.2 computeScrollVectorForPosition(int targetPosition)
java
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
if (getChildCount() == 0) return null;
final int firstChildPos = getPosition(getChildAt(0));
final int direction = targetPosition < firstChildPos ? -1 : 1;
if (canScrollHorizontally()) {
return new PointF(direction, 0);
} else {
return new PointF(0, direction);
}
}
作用:
- 为平滑滚动器提供滚动方向向量
- 帮助确定滚动到目标位置的方向
重写场景:
- 实现
smoothScrollToPosition()
时必须 - 复杂布局需要特殊方向计算时
7. 特殊场景重写方法
7.1 处理适配器变化
java
@Override
public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
removeAllViews(); // 或更精细的处理
}
7.2 处理焦点导航
java
@Override
public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, RecyclerView.State state) {
// 自定义焦点查找逻辑
return findNextFocus(focused, focusDirection);
}
8. 重写方法总结表
方法 | 必须重写 | 主要作用 | 常见重写场景 |
---|---|---|---|
generateDefaultLayoutParams() |
✅ | 生成默认布局参数 | 所有自定义 LayoutManager |
onLayoutChildren() |
✅ | 核心布局方法 | 所有自定义 LayoutManager |
canScrollHorizontally/Vertically() |
❌ | 声明滚动支持 | 支持滚动的布局 |
scrollHorizontallyBy/VerticallyBy() |
❌ | 处理滚动事件 | 支持滚动的布局 |
onMeasure() |
❌ | 自定义测量逻辑 | 需要 wrap_content 的布局 |
scrollToPosition() |
❌ | 立即滚动定位 | 需要程序化滚动的场景 |
smoothScrollToPosition() |
❌ | 平滑滚动定位 | 需要动画滚动的场景 |
supportsPredictiveItemAnimations() |
❌ | 预测动画支持 | 需要流畅动画的场景 |
computeScrollVectorForPosition() |
❌ | 滚动方向计算 | 实现平滑滚动时 |
computeHorizontal/VerticalScrollRange() |
❌ | 滚动范围计算 | 自定义滚动条行为 |
9. 最佳实践建议
- 性能优先 :在
onLayoutChildren()
中尽量减少测量和布局操作 - 合理回收:及时回收不可见视图,避免内存泄漏
- 边界检查:在滚动方法中做好边界检查,避免越界
- 状态保存 :根据需要实现
onSaveInstanceState()
和onRestoreInstanceState()
- 测试全面:测试各种场景:空数据、少量数据、大量数据、数据变化等
通过合理重写这些方法,你可以创建出功能强大、性能优异的自定义 LayoutManager。