需求:
要实现一个横向分页可滑动的显示效果,查了下网上的实现方式,发现一个效果一样的,参考连接如下:
一行代码让RecyclerView分页滚动
但是实现以后发现应用运行起来巨卡,参考了张旭童的文章(掌握自定义LayoutManager),发现这篇文中在onLayoutChildren中会遍历所有的子item并执行如下语句:
View view = recycler.getViewForPosition(index);
和同事一快优化时,同事说每次走这个都会走onCreateViewHolder,因此这个地方需要优化,在优化的过程中参考了以下原则:
1.通过getChildCount()和recycler.getScrapList().size() 查看当前屏幕上的Item数量 和 scrapCache缓存区域的Item数量,合格的LayoutManager,childCount数量不应大于屏幕上显示的Item数量,而scrapCache缓存区域的Item数量应该是0.
2.自己定义的adapter中onCreateViewHolder和onBindeViewHolder的打印次数。
如下是优化后的代码文件,大家可做参考,当然应用卡的根本原因是:应用调试一直是debug版本,打包成release后解决了这个问题,不过因为这个卡顿倒是好好看了一把recyclerview的复用和回收机制,更感谢一起优化的同事,废话不多说,上代码(显示效果是3行6列,因此一页是18个数据):
package com.hisense.recipe.recycleviewpage;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.View;
import com.hisense.fridge.foodmanage.utils.FoodLog;
import java.util.List;
import static com.hisense.recipe.view.magicindicator.ScrollState.SCROLL_STATE_IDLE;
public class HorizontalPageLayoutManager extends RecyclerView.LayoutManager implements PageDecorationLastJudge {
private final String TAG = HorizontalPageLayoutManager.class.getSimpleName();
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return null;
}
int totalHeight = 0;
int totalWidth = 0;
int offsetY = 0;
int offsetX = 0;
private int mFirstVisibleItemPos; //第一个可见的Item索引
private int endIndex;
private View firstView = null;
private SparseArray allItemFrames = new SparseArray<>();
private SparseBooleanArray mHasAttachedItems = new SparseBooleanArray();//布局好的item
RecyclerView.Recycler mRecycler;
public HorizontalPageLayoutManager(int rows, int columns) {
this.rows = rows;
this.columns = columns;
this.onePageSize = rows * columns;
FoodLog.d(TAG, "rows = " + rows + ",columns = " + columns);
}
@Override
public boolean canScrollHorizontally() {
return true;
}
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() <= 0) {
return dx;
}
mRecycler = recycler;
int newX = offsetX + dx;
int result = dx;
if (newX > totalWidth) {
result = totalWidth - offsetX;
} else if (newX < 0) {
result = 0 - offsetX;
}
offsetX += result;
offsetChildrenHorizontal(-result);
layoutScrollChildren(recycler,result);
FoodLog.d(TAG, "scrollHorizontallyBy ~~~~ getScrapList = " + recycler.getScrapList().size() + ",getChildCount = " + getChildCount());
return result;
}
@Override
public void onScrollStateChanged(int state) {
if (state == SCROLL_STATE_IDLE) {
//detachAndScrapAttachedViews(mRecycler);
//recycleChildren(mRecycler);
}
super.onScrollStateChanged(state);
}
private int getUsableWidth() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
private int getUsableHeight() {
return getHeight() - getPaddingTop() - getPaddingBottom();
}
int rows = 0;
int columns = 0;
int pageSize = 0;
int itemWidth = 0;
int itemHeight = 0;
int onePageSize = 0;
int itemWidthUsed;
int itemHeightUsed;
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//如果没有item,直接返回
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
return;
}
// 跳过preLayout,preLayout主要用于支持动画
if (state.isPreLayout()) {
return;
}
//获取每个Item的平均宽高
itemWidth = getUsableWidth() / columns;
itemHeight = getUsableHeight() / rows;
//计算宽高已经使用的量,主要用于后期测量
itemWidthUsed = (columns - 1) * itemWidth;
itemHeightUsed = (rows - 1) * itemHeight;
//计算总的页数
computePageSize(state);
//计算可以横向滚动的最大值
totalWidth = (pageSize - 1) * getWidth();
firstView = recycler.getViewForPosition(0);
addView(firstView);
//测量item
measureChildWithMargins(firstView, itemWidthUsed, itemHeightUsed);
removeAndRecycleView(firstView,recycler);
mHasAttachedItems.clear();
//在布局之前,将所有的子View先Detach掉,放入到Scrap缓存中
detachAndScrapAttachedViews(recycler);
getAllItemFrams(recycler);
recycleAndFillItems(recycler,state);
}
private void computePageSize(RecyclerView.State state) {
pageSize = state.getItemCount() / onePageSize + (state.getItemCount() % onePageSize == 0 ? 0 : 1);
}
private void getAllItemFrams(RecyclerView.Recycler recycler) {
int width = 160;
int height =194;
if (firstView != null){
width = getDecoratedMeasuredWidth(firstView);
height = getDecoratedMeasuredHeight(firstView);
FoodLog.d(TAG, "getAllItemFrams firstView != null " + width + "," + height);
}
int count = getItemCount();
for (int p = 0; p < pageSize; p++) {// 总页数
for (int r = 0; r < rows; r++) {// ==3 3行
for (int c = 0; c < columns; c++) {// == 6 6列
//onePageSize 每一页显示的item数
int index = p * onePageSize + r * columns + c;
if (index == count) {
//跳出多重循环
c = columns;
r = rows;
p = pageSize;
break;
}
//记录显示范围
Rect rect = allItemFrames.get(index);
if (rect == null) {
rect = new Rect();
}
int x = p * getUsableWidth() + c * itemWidth;
int y = r * itemHeight;
rect.set(x, y, width + x, height + y);
// 将当前的Item的Rect边界数据保存
allItemFrames.put(index, rect);
mHasAttachedItems.put(index,false);
}
}
}
FoodLog.d(TAG, "getAllItemFrams allItemFrames size = " + allItemFrames.size());
}
@Override
public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
super.onDetachedFromWindow(view, recycler);
offsetX = 0;
offsetY = 0;
}
private void recycleAndFillItems(RecyclerView.Recycler recycler, RecyclerView.State state) {
initNeedLayoutItems(state);
layoutChildren(recycler);
recycleChildren(recycler);
FoodLog.d(TAG, "recycleAndFillItems ~~~~ getScrapList = " + recycler.getScrapList().size() + ",getChildCount = " + getChildCount());
}
/**
* 初始化需要布局的Item数据
*/
private void initNeedLayoutItems(RecyclerView.State state) {
Rect visibleRect = getVisibleArea();
for (int i = 0; i < getItemCount(); i++) {
Rect rect = allItemFrames.get(i);
if (Rect.intersects(visibleRect, rect)) {
mFirstVisibleItemPos = i;
break;
}
}
endIndex = mFirstVisibleItemPos + 18;
int totalItemCount = state == null ? getItemCount() : state.getItemCount();
if (endIndex > totalItemCount) {
endIndex = totalItemCount;
}
FoodLog.d(TAG,"initNeedLayoutItems mFirstVisibleItemPos = " + mFirstVisibleItemPos + ",endIndex = " + endIndex);
}
/*
* 布局需要显示的item
* */
private void layoutChildren(RecyclerView.Recycler recycler) {
for (int i = mFirstVisibleItemPos; i < endIndex; i++) {
View viewForPosition = recycler.getViewForPosition(i);
Rect rect = allItemFrames.get(i);
addView(viewForPosition);
measureChildWithMargins(viewForPosition, 0, 0);
layoutDecorated(viewForPosition, rect.left - offsetX, rect.top, rect.right - offsetX, rect.bottom);
}
/*Rect visibleRect = new Rect(getPaddingLeft() + offsetX, getPaddingTop(), getWidth() - getPaddingLeft() - getPaddingRight() + offsetX, getHeight() - getPaddingTop() - getPaddingBottom());
for (int i = 0; i < getItemCount(); i++) {
Rect rect = allItemFrames.get(i);
if (Rect.intersects(visibleRect, rect)) {
View viewForPosition = recycler.getViewForPosition(i);
addView(viewForPosition);
measureChildWithMargins(viewForPosition, 0, 0);
layoutDecorated(viewForPosition, rect.left - offsetX, rect.top, rect.right - offsetX, rect.bottom);
}
}*/
}
private void layoutScrollChildren(RecyclerView.Recycler recycler,int result){
Rect visibleRect = getVisibleArea();
//回收越界子View
for (int i = getChildCount() - 1; i >= 0; i--) {
View child = getChildAt(i);
int position = getPosition(child);
Rect rect = allItemFrames.get(position);
if (!Rect.intersects(rect, visibleRect)) {
removeAndRecycleView(child, recycler);
mHasAttachedItems.put(position,false);
} else {
layoutDecorated(child, rect.left - offsetX, rect.top, rect.right - offsetX, rect.bottom);
mHasAttachedItems.put(position, true);
}
}
View lastView = getChildAt(getChildCount() - 1);
View firstView = getChildAt(0);
if (result >= 0) {
int minPos = getPosition(firstView);
for (int i = minPos; i < getItemCount(); i++) {
insertView(i, visibleRect, recycler, false);
}
} else {
int maxPos = getPosition(lastView);
for (int i = maxPos; i >= 0; i--) {
insertView(i, visibleRect, recycler, true);
}
}
}
private void insertView(int pos, Rect visibleRect, RecyclerView.Recycler recycler, boolean firstPos) {
Rect rect = allItemFrames.get(pos);
if (Rect.intersects(visibleRect, rect) && !mHasAttachedItems.get(pos)) {
View child = recycler.getViewForPosition(pos);
if (firstPos) {
addView(child, 0);
} else {
addView(child);
}
measureChildWithMargins(child, 0, 0);
layoutDecorated(child, rect.left - offsetX, rect.top, rect.right - offsetX, rect.bottom);
mHasAttachedItems.put(pos,true);
}
}
private Rect getVisibleArea(){
Rect visibleRect = new Rect(getPaddingLeft() + offsetX, getPaddingTop(), getWidth() - getPaddingLeft() - getPaddingRight() + offsetX, getHeight() - getPaddingTop() - getPaddingBottom());
return visibleRect;
}
/**
* 回收屏幕外需回收的Item
*/
private void recycleChildren(RecyclerView.Recycler recycler) {
List scrapList = recycler.getScrapList();
for (int i = 0; i < scrapList.size(); i++) {
RecyclerView.ViewHolder holder = scrapList.get(i);
removeView(holder.itemView);
recycler.recycleView(holder.itemView);
}
}
@Override
public boolean isLastRow(int index) {
if (index >= 0 && index < getItemCount()) {
int indexOfPage = index % onePageSize;
indexOfPage++;
if (indexOfPage > (rows - 1) * columns && indexOfPage <= onePageSize) {
return true;
}
}
return false;
}
@Override
public boolean isLastColumn(int position) {
if (position >= 0 && position < getItemCount()) {
position++;
if (position % columns == 0) {
return true;
}
}
return false;
}
@Override
public boolean isPageLast(int position) {
position++;
return position % onePageSize == 0;
}
@Override
public int computeHorizontalScrollRange(RecyclerView.State state) {
computePageSize(state);
return pageSize * getWidth();
}
@Override
public int computeHorizontalScrollOffset(RecyclerView.State state) {
return offsetX;
}
@Override
public int computeHorizontalScrollExtent(RecyclerView.State state) {
return getWidth();
}
}