“傻瓜”式填充,自定义LayoutManager

“傻瓜”式填充,自定义LayoutManager

RecycleView的高效是众所周知的事了,官方提供的LayoutManager基本上能满足90%上以的界面开发了,但难免会有一些“非人类”设计师会想出的界面,例如机顶盒上不规则的gridlayout了,但是这种情况也可以通过自定义的LayoutManager来解决
在自定义LayoutManager前,我们先来简单的学习一下RecycleView的两级缓存:scrapt和recycle。
scrap Heap,当需要view时,recycle会来这里查找有没有所需要的view,注意这里的view是可以直接使用的,即不会再触发bindViewHolder 了,我们可以通过detachAndScrapView方法将view回收到scrap
Recycle Pool,当在scrap里没有找到直接的view可以用时,会来recycle里查找是否有可以间接使用的view,即通过适配器重新绑定数据bindViewHolder后可以使用。我们可以通过removeAndRecycleView来将view回收至recycle里。
先看效果:这里写图片描述
代码:

public class HomeLayoutManager extends RecyclerView.LayoutManager {
    private LayoutState mLayoutState;
    private ArrayList<RectF> mItemRectFList;

    //用来判断是否到边界了
    private int mTotalW;

    //主要用来标志item是否已在当前界面上
    private final HiveBucket mBooleanMap = new HiveBucket();

    public HomeLayoutManager(ArrayList<RectF> itemRectFList) {
        mItemRectFList = itemRectFList;
        init();
    }

    private void init() {
        mLayoutState = new LayoutState();

        mBooleanMap.reset();
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//        super.onLayoutChildren(recycler, state);
        int itemCount = state.getItemCount();
        if (itemCount <= 0) {
            return;
        }
        if (state.isPreLayout()) {
            return;
        }
        //回收当前所有的view到Scrap里
        detachAndScrapAttachedViews(recycler);
        //初始化标志位
        mBooleanMap.reset();
        // 遍历所有的item
        fill(recycler, state);

    }


    private void fill(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (state.isPreLayout()) {
            return;
        }
        //更新recycleview的控件窗口位置
        updateLayoutState();
        mTotalW = 0;
        int itemCount = state.getItemCount();
        for (int i = 0; i < itemCount; i++) {
            // 得到当前position下的视图显示区域
            RectF bounds = new RectF(mItemRectFList.get(i));
            //需要根据你的实际情况来设置边界值
            if (bounds.right > mTotalW) {
                mTotalW = (int) bounds.right;
            }

            //注:这边事先获取item的位置来判断是否要显示在屏幕上后再来获取itemView的对象,所以itemView的位置信息由外部传进来
            if (!mBooleanMap.get(i) && RectF.intersects(bounds, mLayoutState.containerRect)) {
                // 通过recycler得到该位置上的View,Recycler负责是否使用旧的还是生成新的View。
                View view = recycler.getViewForPosition(i);

                bounds.offset(mLayoutState.offsetX, mLayoutState.offsetY);
                // 然后我们将得到的View添加到Recycler中
                addView(view);
                //标志当前item已在界面上
                mBooleanMap.set(i);
                // 然后测量View带Margin的的尺寸
                measureChildWithMargins(view, 0, 0);
                // 然后layout带Margin的View,将View放置到对应的位置
                layoutDecoratedWithMargins(view, (int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom);
            }

        //预留右边50的边界,左边50的边界已在item位置里
        mTotalW += 50;
    }

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    //水平滑动开关
    @Override
    public boolean canScrollHorizontally() {
        return true;
    }

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
//

        //实际要滑动的距离
        int travel = dx;

        //判断是否到边界
        //如果滑动到最左边
        if (-mLayoutState.offsetX + dx < 0) {
            travel = mLayoutState.offsetX;
        } else if (-mLayoutState.offsetX + dx + getWidth() > mTotalW) {//如果滑动到最右边
            travel = mTotalW + mLayoutState.offsetX - getWidth();
        }


        //移动
        offsetChildrenHorizontal(-travel);
        //记录当前移动距离,绘制界面时需要用到
        mLayoutState.offsetX -= travel;
        //回收view的Recycle
        scrapOutSetViews(recycler);
        //重新绘制界面:在滑动过程中需要将新进入屏幕的view设置出来
        fill(recycler, state);
        return travel;
    }

    private void scrapOutSetViews(RecyclerView.Recycler recycler) {
        int count = getChildCount();
        for (int i = count - 1; i >= 0; i--) {
            View view = getChildAt(i);
            if (!RectF.intersects(new RectF(0, 0, getWidth(), getHeight()), new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()))) {
                int position = getPosition(view);
                mBooleanMap.clear(position);
                removeAndRecycleViewAt(i, recycler);
            }
        }
    }

    //更新父控件窗口位置
    private void updateLayoutState() {
        mLayoutState.containerRect.set(0, 0, getWidth(), getHeight());
        mLayoutState.containerRect.offset(-mLayoutState.offsetX, -mLayoutState.offsetY);
    }

    private class LayoutState {

        int offsetX;
        int offsetY;

        final RectF containerRect = new RectF();
    }
}

注释在代码中已经写得很清楚了,这里采用的是横向滑动,当然也可以通过重写canScrollVertically和scrollVerticallyBy来实现垂直方向的滑动。

最后捧上git地址:https://github.com/jackliy/CusLayoutManager

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值