优化横向分页自定义LayoutManager-HorizontalPageLayoutManager

需求:
要实现一个横向分页可滑动的显示效果,查了下网上的实现方式,发现一个效果一样的,参考连接如下:
一行代码让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<Rect> 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<RecyclerView.ViewHolder> 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();
    }
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值