自定義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);
}