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

来源:互联网 发布:netbeans php下载 编辑:程序博客网 时间:2024/05/17 17:55

“傻瓜”式填充,自定义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

原创粉丝点击