自定义LayoutManager的步骤:
总体可分为四步:
- 重写generateDefaultLayoutParams()
- 重写 onLayoutChildren()
- 重写 canScrollVertically() 或者 canScrollHorizontally()
- 重写 scrollHorizontallyBy() 或者 scrollVerticallyBy()
这些都是些什么。别慌,一步步来,下面会以可横向滚动layoutManager来一一解释这些方法。首先得注意无论是在onLayoutChildren、scrollHorizontallyBy() 或者 scrollVerticallyBy()RecyclerView只会layout出可见的childview,不可见的childView会被移除、回收掉
步骤1
-
方法解释
generateDefaultLayoutParams():为RecyclerView的子View(也就是我们常说的Itme)创建一个默认的LayoutParams对象SDK 解释如下
Create a default LayoutParams object for a child of the RecyclerView. 实现,没有特殊要求如下实现方式即可
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(
RecyclerView.LayoutParams.WRAP_CONTENT,
RecyclerView.LayoutParams.WRAP_CONTENT);
}
步骤2
-
方法解释
onLayoutChildren():从给定适配器设置所有相关的子视图.在初始化或者调用notifyDataSetChanged()时调用该方法,只需要layout 显示的childView即可SDK 解释如下
Lay out all relevant child views from the given adapter 实现
- 常量、变量解释
//RecyclerView从右往左滑动时,新出现的child添加在右边
private static int ADD_RIGHT = 1;
//RecyclerView从左往右滑动时,新出现的child添加在左边
private static int ADD_LEFT = -1;
//SDK中的方法,帮助我们计算一些layout childView 所需的值,详情看源码,解释的很明白
private OrientationHelper helper;
//动用 scrollToPosition 后保存去到childView的位置,然后重新布局即调用onLayoutChildren
private int mPendingScrollPosition = 0;
- onLayoutChildren 实现
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getItemCount() == 0) {
detachAndScrapAttachedViews(recycler);
return;
}
if (getChildCount() == 0 && state.isPreLayout()) {
return;
}
//初始化OrientationHelper
ensureStatus();
int offset = 0;
//计算RecyclerView 可用布局宽度 具体实现 mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
//- mLayoutManager.getPaddingRight();
int mAvailable = helper.getTotalSpace();
//调用notifyDataSetChanged 才有 getChildCount() != 0
if (getChildCount() != 0) {
//得到第一个可见的childView
View firstView = getChildAt(0);
//得到第一个可见childView左边的位置
offset = helper.getDecoratedStart(firstView);
//获取第一个可见childView在Adapter中的position(位置)
mPendingScrollPosition = getPosition(firstView);
//offset的值为负数,在可见区域的左边,那么当重新布局事得考虑正偏移
mAvailable += Math.abs(offset);
}
//移除所有的view,
detachAndScrapAttachedViews(recycler);
//将可见区域的childView layout出来
layoutScrap(recycler, state, offset, mAvailable);
}
- 初始化OrientationHelper
private void ensureStatus() {
if (helper == null) {
helper = OrientationHelper.createHorizontalHelper(this);
}
}
- 将可见区域的childView layout出来
/**
* 将可见区域的childView layout出来
*/
private void layoutScrap(RecyclerView.Recycler recycler, RecyclerView.State state, int offset, int mAvailable) {
for (int i = mPendingScrollPosition; i < state.getItemCount(); i++) {
//可用布局宽度不足,跳出循环
if (mAvailable <= 0) {
break;
}
//在右边添加新的childView
int childWidth = layoutScrapRight(recycler, i, offset);
mAvailable -= childWidth;
offset += childWidth;
}
}
- 添加新的childView
/**
* RecyclerView从右往左滑动时,新出现的child添加在右边
*/
private int layoutScrapRight(RecyclerView.Recycler recycler, int position, int offset) {
return layoutScrap(recycler, position, offset, ADD_RIGHT);
}
/**
* RecyclerView从右往左滑动时,新出现的child添加在右边
*/
private int layoutScrapleft(RecyclerView.Recycler recycler, int position, int offset) {
return layoutScrap(recycler, position, offset, ADD_LEFT);
}
/**
* RecyclerView从右往左滑动时,添加新的child
*/
private int layoutScrap(RecyclerView.Recycler recycler, int position, int offset, int direction) {
//从 recycler 中取到将要出现的childView
View childPosition = recycler.getViewForPosition(position);
if (direction == ADD_RIGHT) {
addView(childPosition);
} else {
addView(childPosition, 0);
}
//计算childView的大小
measureChildWithMargins(childPosition, 0, 0);
int childWidth = getDecoratedMeasuredWidth(childPosition);
int childHeigth = getDecoratedMeasuredHeight(childPosition);
if (direction == ADD_RIGHT) {
//layout childView
layoutDecorated(childPosition, offset, 0, offset + childWidth, childHeigth);
} else {
layoutDecorated(childPosition, offset - childWidth, 0, offset, childHeigth);
}
return childWidth;
}
步骤3
方法解释
canScrollVertically():竖直方向是否可以滚动
canScrollHorizontally():水平方向是否可以滚动实现 ,本例只实现canScrollHorizontally
@Override
public boolean canScrollHorizontally() {
return true;
}
步骤4
方法解释
scrollVerticallyBy():处理竖直方向滚动时不可见的childView的回收,新出现childview的添加
scrollHorizontallyBy():处理水平方向滚动时不可见的childView的回,收新出现childview的添加实现 ,本例只实现scrollHorizontallyBy
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
//回收不可见的childview
recylerUnVisiableView(recycler, dx);
//将新出现的childview layout 出来
int willScroll = fillChild(recycler, dx, state);
//水平方向移动childview
offsetChildrenHorizontal(-willScroll);
return willScroll;
}
private int fillChild(RecyclerView.Recycler recycler, int dx, RecyclerView.State state) {
if (dx > 0) {//RecyclerView从右往左滑动时
//得到最后一个可见childview
View lastView = getChildAt(getChildCount() - 1);
//得到将显示的childView 在adapter 中的position
int position = getPosition(lastView) + 1;
//得到最后一个可见childView右边的偏移
int offset = helper.getDecoratedEnd(lastView);
//判断是否有足够的空间
if (offset - dx < getWidth() - getPaddingRight()) {
//item 足够
if (position < state.getItemCount()) {
layoutScrapRight(recycler, position, offset);
} else {
//item 不足 返回新的可滚动的宽度
return offset - getWidth() + getPaddingRight();
}
}
} else {//RecyclerView从左往右滑动时
//得到第一个可见childview
View firstView = getChildAt(0);
//得到将显示的childView 在adapter 中的position
int position = getPosition(firstView) - 1;
//得到第一个可见childView左边的偏移
int offset = helper.getDecoratedStart(firstView);
//判断是否有足够的空间
if (offset - dx > getPaddingLeft()) {
//item 足够
if (position >= 0) {
layoutScrapleft(recycler, position, offset);
} else {
//item 不足 返回新的可滚动的宽度
return offset - getPaddingLeft();
}
}
}
return dx;
}
/**
* 回收不可见的childview
*/
private void recylerUnVisiableView(RecyclerView.Recycler recycler, int dx) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (dx > 0) {//RecyclerView从右往左滑动时
//将左边消失的childView 回收掉
if (helper.getDecoratedEnd(child) - dx < getPaddingLeft()) {
removeAndRecycleView(child, recycler);
break;
}
} else {//RecyclerView从左往右滑动时
//将右边的childView 回收掉
if (helper.getDecoratedStart(child) - dx > getWidth() - getPaddingRight()) {
removeAndRecycleView(child, recycler);
break;
}
}
}
}
扩展
- scrollToPosition 滑动到指定位置,调用该方法后,会调用onLayoutChildren
@Override
public void scrollToPosition(int position) {
super.scrollToPosition(position);
mPendingScrollPosition = position;
requestLayout();
}
- smoothScrollToPosition 缓慢的滚动到指定位置
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
LinearSmoothScroller linearSmoothScroller =
new LinearSmoothScroller(recyclerView.getContext()) {
@Nullable
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
if (getChildCount() == 0) {
return null;
}
final int firstChildPos = getPosition(getChildAt(0));
final int direction = targetPosition < firstChildPos ? -1 : 1;
return new PointF(direction, 0);
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}