转载请注明出处:
http://blog.csdn.net/user11223344abc/article/details/78080671
出自【蛟-blog】
本文分为俩个Step来研究如何自定义一个合格的LinearlayoutMnager。
内容涉及到部分原理,更多是代码层面的讲解,就是说,代码为什么这样写
Ps:有什么问题请直接指出,欢迎拍砖。
上篇(见title)初步认识了自定义layoutManager是怎么回事,也定义了一个可以滑动的VerticalLinlearLayoutManager,建议先看下之前的代码。在这里,我提出问题,然后分析如何解决。
之前我们初始化,是按照这么一个模式进行的。
forI(getItemCount){
addView...
layoutView...
}
这么做的问题,就是假设有十万条数据,那么就直接炸了。
思路已经理出来了,但要落实到代码上,还需要费一番功夫。
/**
* 获取可见的区域Rect
* @return
*/
private Rect getVisibleArea() {
Rect result = new Rect(getPaddingLeft(), getPaddingTop() + mTheMoveDistance, getWidth() + getPaddingRight(), getVerticalVisibleHeight() + mTheMoveDistance);
return result;
}
由于可见范围随着滑动而改变,所以这里用了一个变量mTheMoveDistance,上一篇博客有关于它的详细讲解。
private int initStepHeight(RecyclerView.Recycler recycler) {
View Adam = recycler.getViewForPosition(0);
addView(Adam);
int result = -1;
measureChildWithMargins(Adam, 0, 0);
result = getDecoratedMeasuredHeight(Adam);
removeAndRecycleAllViews(recycler);
return result;
}
这个Adam为首个条目(意为亚当),有了这个均高,我们才能算出可见屏幕能容纳多少条item。
/**
* 初始化可见条目数,addview以及layout
*/
private int initVisibleCounts(int stepHeight) {
int verticalVisibleHeight = getVerticalVisibleHeight();
return verticalVisibleHeight / stepHeight;
}
/**
* addView * layoutDecorated
*
* @param recycler
* @param visibleCount
*/
private void layoutChildren(RecyclerView.Recycler recycler, int visibleCount) {
detachAndScrapAttachedViews(recycler);
for (int i = 0; i < visibleCount; i++) {
View viewForPositionChild = recycler.getViewForPosition(i);
measureChildWithMargins(viewForPositionChild, 0, 0);
addView(viewForPositionChild);
//TODO 可以判空
layoutDecorated(viewForPositionChild, itemRects.get(i).left, itemRects.get(i).top, itemRects.get(i).right, itemRects.get(i).bottom);
}
}
这方法的意思就是,在可见范围内填充应该填充条目的数量。(基于上面几个步骤运算得出的结果)。
这里我先不贴代码,等分析完回收复用的逻辑之后在后面贴出代码,这里只是表示我们需要再初始化的时候初始化一个缓存容器。
回收的api我们在上一篇就已经过了一遍,这里我再贴出来,方便记忆:
回收API
scrap >> detachAndScrapView()
Recycle >> removeAndRecycleView()
那么它应该写在哪呢?
首先因为我们初始化已经进行过优化,所以回收逻辑我们只需要写在滑动的方法内,也就是像verticalscrollby这种方法。并且也可以这么理解,离屏回收嘛,肯定是需要我们滑动的时候,条目离开了可见范围的时候才进行回收。
getViewForPosition
↓
tryGetViewHolderForPositionByDeadline
↓
tryBindViewHolderByDeadline$Recycler
↓
bindViewHolder$Adapter
↓
onBindViewHolder$Adapter
这里在onCreateViewHolder了少数条目之后,多数是调了onBindViewHolder,那就说明是复用了。
滑动的时候,判断,如果条目没有在可见范围内(可见范围是我们初始化的时候就已经计算出来的了),就进行一个回收。若在可见范围内就从从RecyclerView二级缓存内去取出条目填充到列表之中。
需要注意的是,我们填充一个条目到recyclerView的时候是需要穿参一个Rect,也就是需要知道每个条目的位置信息,而这个位置信息我们需要再初始化的时候根据条目均高来计算并放入到一个SparseArray之内,还记得刚才我初始化的代码有一个没贴代码么?【1.3.5 其他扩展(为回收做准备)】,这里我把代码贴出来。
/**
* 记录所有条目的位置
* 以及totalHeight
*/
private void initItemRectSparse(int stepItemHeight) {
itemRects = new SparseArray<>();
int offsetY = getPaddingTop();
for (int i = 0; i < getItemCount(); i++) {
Log.e("initItemRectSparse", "SparseArray item>>" + i + "__上:" + offsetY + "__下:" + (offsetY + stepItemHeight));
itemRects.put(i, new Rect(getPaddingLeft(), offsetY, getWidth() + getPaddingRight(), offsetY + stepItemHeight));
offsetY += stepItemHeight;
mTotalHeight = offsetY;
}
}
接下来我把整个回收复用的逻辑贴出来。
/**
* 处理离屏回收
*
* @param recycler
* @param state
*/
private void handleRecycle(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (cut(state)) return;
detachAndScrapAttachedViews(recycler);
int childCount = getChildCount();
Log.e("handleRecycle", childCount + "");
Rect visibleRect = getVisibleArea();
for (int i = 0; i < getItemCount(); i++) {
View viewForPosition = recycler.getViewForPosition(i);
Rect rect = itemRects.get(i);
if (Rect.intersects(visibleRect, rect)) {
addView(viewForPosition);
measureChildWithMargins(viewForPosition, 0, 0);
layoutDecorated(viewForPosition, rect.left, rect.top - mTheMoveDistance, rect.right, rect.bottom - mTheMoveDistance);
} else {
removeAndRecycleView(viewForPosition, recycler);
Log.e("handleRecycle","回收喽");
}
}
}
调用:
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
dy = handleScroll(dy, recycler, state);
handleRecycle(recycler, state);
return dy;
}
有问题请直接指出,demo地址下载