RecycleView自定义LayoutManager(一)

2018-12-19  本文已影响121人  digtal_

1.纵向Layoutmanager(VerticalLayoutManager)

  1. 先写一个类继承Layoutmanager,默认要实现generateDefaultLayoutParams方法,一般没有要修改itemview布局参数的话,默认就按下面来写
    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT,
                RecyclerView.LayoutParams.WRAP_CONTENT);
    }

2.如果只重写这个方法的话,我们运行起来会发现什么都没有显示,这是因为所有的itemView的布局都是在onLayoutChildren方法中,所以我们要重写这个方法来布局所有的itemview

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getItemCount() == 0) {
            return;
        }
        //摆放可见的itemview
        int temp = 0;
        for (int i = 0; i < getItemCount(); i++) {
            View child = recycler.getViewForPosition(i);
            measureChildWithMargins(child, 0, 0);
            int itemWidth = getDecoratedMeasuredWidth(child);
            int itemHeight = getDecoratedMeasuredHeight(child);
            addView(child);
            measureChildWithMargins(child, 0, 0);
            layoutDecoratedWithMargins(child, getPaddingLeft(), temp, itemWidth, temp + itemHeight);
            temp += itemHeight;
        }  
         mTotalHeight = Math.max(getVerticalSpace(), temp);
    }

3.你会发现此时不可以上下滑动,想要上下滑动还需要重写下面二个方法

    @Override
    public boolean canScrollVertically() {
        return true;
    }
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    offsetChildrenVertical(-dy);
    return dy;
  }
1.gif

4.你会发现itemview可以拖出屏幕之外,所以要对拖动范围进行限制,上滑不能超出RecycleView高度,下滑不能滑动到0以下

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getItemCount() == 0) {
            return dy;
        }
        int travel = dy;
        //限定拖动范围
        if (mTotalMoveY + travel < 0) {
            travel = -mTotalMoveY;
        } else if (mTotalMoveY + travel > mTotalHeight - getVerticalSpace()) {
            travel = mTotalHeight - getVerticalSpace() - mTotalMoveY;
        }
        mTotalMoveY += travel;
        offsetChildrenVertical(-travel);
        return travel;
    }

2.纵向复用(VerticalLayoutManagerRecycled)

1.上面效果看上去和LinerLayoutManager没有什么区别,但是我们在Adapter的onCreateViewHolder和onBindViewHolder方法中打印Log会发现有多少条目就同时调用了多少次onCreateViewHolder和onBindViewHolder方法,onCreateViewHolder执行一次代表创建了一个itemview,这就代表没有复用itemview,在数据量大的情况下就会发生anr异常

2.复用用到的几个重要的方法:

3.具体代码

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getItemCount() == 0) {
            return;
        }
        detachAndScrapAttachedViews(recycler);
        View view = recycler.getViewForPosition(0);
        measureChildWithMargins(view, 0, 0);
        int itemWidth = getDecoratedMeasuredWidth(view);
        int itemHeight = getDecoratedMeasuredHeight(view);
        //屏幕上可见的itemView个数
        int visible = getVerticalSpace() / itemHeight;

        int temp = 0;
        for (int i = 0; i < getItemCount(); i++) {
            Rect rect = new Rect(getPaddingLeft(), temp, itemWidth, itemHeight + temp);
            mRectArray.put(i, rect);
            temp += itemHeight;
        }
        //摆放可见的itemview
        for (int i = 0; i < visible; i++) {
            Rect rect = mRectArray.get(i);
            View child = recycler.getViewForPosition(i);
            addView(child);
            measureChildWithMargins(child, 0, 0);
            layoutDecorated(child, rect.left, rect.top, rect.right, rect.bottom);
        }

        mTotalHeight = Math.max(getVerticalSpace(), temp);
    }
 //dy>0 👆 dy<0👇
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getItemCount() == 0) {
            return dy;
        }
        int travel = dy;
        if (travel + mTotalMoveY < 0) {
            travel = -mTotalMoveY;
        } else if (travel + mTotalMoveY > mTotalHeight - getVerticalSpace()) {
            travel = mTotalHeight - getVerticalSpace() - mTotalMoveY;
        }
        //回收不在屏幕内的itemview
      for (int i = getChildCount()-1; i >=0; i--) {
            View view = getChildAt(i);
            //下滑回收上面移出屏幕的itemview
            if (getDecoratedBottom(view) - travel < 0) {
                removeAndRecycleView(view, recycler);
                //上滑回收下面移出屏幕的itemview
            } else if (getDecoratedTop(view) - travel > getVerticalSpace()) {
                removeAndRecycleView(view, recycler);
            }
        }
        //获取当前可见的屏幕区域
        Rect visibleRect = getVisibleRect(travel);
        //移动时将回收的itemview从缓存中取出来
        if (travel > 0) {
            //上滑即将出来的条目的索引
            int next = getPosition(getChildAt(getChildCount() - 1)) + 1;
            for (int i = next; i < getItemCount(); i++) {
                insertView(recycler, visibleRect, i, false);
            }
        } else {
            //下滑即将出来的条目的索引
            int last = getPosition(getChildAt(0)) - 1;
            for (int i = last; i >= 0; i--) {
                insertView(recycler, visibleRect, i, true);
            }
        }

        mTotalMoveY += travel;
        offsetChildrenVertical(-travel);
        return travel;
    }
    private void insertView(RecyclerView.Recycler recycler, Rect visibleRect, int pos, boolean flag) {
        Rect rect = mRectArray.get(pos);
        if (Rect.intersects(visibleRect, rect)) {
            View child = recycler.getViewForPosition(pos);
            if (flag) {
                addView(child, 0);
            } else {
                addView(child);
            }
            measureChildWithMargins(child, 0, 0);
            layoutDecoratedWithMargins(child, rect.left, rect.top - mTotalMoveY,
                    rect.right, rect.bottom - mTotalMoveY);
        }
    }

   private int getVerticalSpace() {
        return getHeight() - getPaddingBottom() - getPaddingTop();
    }
    private Rect getVisibleRect(int travel) {
        return new Rect(getPaddingLeft(), mTotalMoveY + travel,
                getWidth(), mTotalMoveY + travel + getVerticalSpace());
    }
12-19 11:57:51.996 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+49
12-19 11:57:51.997 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+50
12-19 11:57:51.998 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+51
12-19 11:57:52.012 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+52
12-19 11:57:52.013 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+53
12-19 11:57:52.013 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+54
12-19 11:57:52.029 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+55
12-19 11:57:52.030 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+56
12-19 11:57:52.030 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+57
12-19 11:57:52.045 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+58
12-19 11:57:52.046 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+59
12-19 11:57:52.048 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+60
12-19 11:57:52.061 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+61
12-19 11:57:52.062 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+62
12-19 11:57:52.062 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+63
12-19 11:57:52.078 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+64
12-19 11:57:52.079 19035-19035/com.chinamall21.mobile.study E/Study=: onBindViewHolder+65

3.纵向Layoutmanager带动画(VerticalLayoutManagerAnim)

1.使用offsetChildrenVertical(-travel)函数来移动屏幕中所有item。这种方法仅适用于每个item,在移动时,没有特殊效果的情况,当我们在移动item时,同时需要改变item的角度、透明度等情况时,单纯使用offsetChildrenVertical(-travel)来移是不行的。针对这种情况,我们就只有使用第二种方法来实现回收复用了。
2.我们主要替换掉移动item所用的offsetChildrenVertical(-travel);函数,既然要将它弃用,那我们就只能自己布局每个item了。
3.具体代码

 //是否在当前屏幕的itemView
    private SparseBooleanArray mAttachItems = new SparseBooleanArray();
    for (int i = 0; i < getItemCount(); i++) {
            Rect rect = new Rect(getPaddingLeft(), temp, itemWidth, itemHeight + temp);
            mRectArray.put(i, rect);
            mAttachItems.put(i, false);
            temp += itemHeight;
        }
 //dy>0 👆 dy<0👇
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getItemCount() == 0) {
            return dy;
        }
        int travel = dy;
        if (travel + mTotalMoveY < 0) {
            travel = -mTotalMoveY;
        } else if (travel + mTotalMoveY > mTotalHeight - getVerticalSpace()) {
            travel = mTotalHeight - getVerticalSpace() - mTotalMoveY;
        }

        mTotalMoveY += travel;
        //获取当前可见的屏幕区域
        Rect visibleRect = getVisibleRect();
        //回收不在屏幕内的itemview
        for (int i = getChildCount()-1; i >= 0; i--) {
            View view = getChildAt(i);
            int position = getPosition(view);
            Rect rect = mRectArray.get(position);
            if (Rect.intersects(visibleRect, rect)) {
                layoutDecoratedWithMargins(view, rect.left, rect.top - mTotalMoveY, rect.right, rect.bottom - mTotalMoveY);
                mAttachItems.put(position, true);
                view.setRotationY(view.getRotationY() + 1);
            } else {
                removeAndRecycleView(view, recycler);
                mAttachItems.put(position, false);
            }
        }
        int next = getPosition(getChildAt(getChildCount() - 1));
        int last = getPosition(getChildAt(0));
        //移动时将回收的itemview从缓存中取出来
        if (travel > 0) {
            //上滑即将出来的条目的索引
            for (int i = next; i < getItemCount(); i++) {
                insertView(recycler, visibleRect, i, false);
            }
        } else {
            //下滑即将出来的条目的索引
            for (int i = last; i >= 0; i--) {
                insertView(recycler, visibleRect, i, true);
            }
        }
        return travel;
    }
    private void insertView(RecyclerView.Recycler recycler, Rect visibleRect, int pos, boolean flag) {
        Rect rect = mRectArray.get(pos);
        if (Rect.intersects(visibleRect, rect) && !mAttachItems.get(pos)) {
            View child = recycler.getViewForPosition(pos);
            if (flag) {
                addView(child, 0);
            } else {
                addView(child);
            }
            measureChildWithMargins(child, 0, 0);
            layoutDecoratedWithMargins(child, rect.left, rect.top - mTotalMoveY,
                    rect.right, rect.bottom - mTotalMoveY);
            child.setRotationY(child.getRotationY() + 1);
            mAttachItems.put(pos, true);
        }
    }

4.横向滑动HorizontalLayoutmanager与流失布局效果FlowLayoutmanager

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getItemCount() == 0) {
            return;
        }
        detachAndScrapAttachedViews(recycler);
        int tempHeight = mDefaultMargin;
        int tempWidth = mDefaultMargin;
        for (int i = 0; i < getItemCount(); i++) {
            View view = recycler.getViewForPosition(i);
            addView(view);
            measureChildWithMargins(view, 0, 0);
            int itemWidth = getDecoratedMeasuredWidth(view);
            int itemHeight = getDecoratedMeasuredHeight(view);
            //超出屏幕宽度换行
            if (tempWidth + itemWidth >= getHorizontalSpace()) {
                tempWidth = mDefaultMargin;
                tempHeight += itemHeight + mDefaultMargin;
            }
            layoutDecoratedWithMargins(view, tempWidth, tempHeight, itemWidth + tempWidth, tempHeight + itemHeight);
            tempWidth += itemWidth + mDefaultMargin;
        }
        mTotalHeight = Math.max(tempHeight, getVerticalSpace());
    }

源码地址https://github.com/digtal/recycleview-study

本篇内容参考于https://blog.csdn.net/harvic880925/article/details/84979161

上一篇 下一篇

猜你喜欢

热点阅读