自定义LayoutManager--实现流式布局

整体效果,使用RecyclerView,通过自定义的LayoutManager实现了一个流式布局,在上滑到底部和下滑到顶部时无法滑动,并且每行子item可以测量当宽度不够时自动折行显示的效果,完整项目:https://github.com/buder-cp/CustomView/tree/master/buder_DN_view/buderdn23_layoutmanager

首先我们要准备一个类让它继承RecyclerView.LayoutManager

public class AutoLayoutManager extends RecyclerView.LayoutManager {}

定义完成后, android studio会提醒我们去实现一下RecyclerView.LayoutManager里的一个抽象方法,该方法是生成子控件的LayoutParams

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

这样, 其实一个最简单的LayoutManager我们就完成了, 不过现在在界面上是什么也没有的,因为我们还没有对item view进行布局。在RecyclerView.LayoutManager中布局的入口是一个叫onLayoutChildren的方法,我们来重写这个方法。

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        //摆放
        if (getItemCount() <= 0) {
            return;
        }
        //preLayout主要支持动画,直接跳过
        if (state.isPreLayout()) {
            return;
        }
        //将视图分离放入scrap缓存中,以准备重新对view进行排版
        detachAndScrapAttachedViews(recycler);

        int offsetY = 0;
        int offsetX = 0;
        int viewH = 0;
        for (int i = 0; i < getItemCount(); i++) {
            View view = recycler.getViewForPosition(i);
            addView(view);
            measureChildWithMargins(view, 0, 0);

            int w = getDecoratedMeasuredWidth(view);

            int h = getDecoratedMeasuredHeight(view);
            viewH = h;
            Rect fram = allItemframs.get(i);
            if (fram == null) {
                fram = new Rect();
            }
            //需要换行
            if (offsetX + w > getWidth()) {
                //换行的View的值
                offsetY += h;
                offsetX = w;
                fram.set(0, offsetY, w, offsetY + h);
            } else {
                //不需要换行
                fram.set(offsetX, offsetY, offsetX + w, offsetY + h);
                offsetX += w;
            }
            //要针对于当前View   生成对应的Rect  然后放到allItemframs数组
            allItemframs.put(i, fram);
        }
        totalHeight = offsetY + viewH;
        //detach  轻量级的移除操作;remove  重量级
        //回收不可见的
        recyclerViewFillView(recycler, state);
    }

这个方法总共会调用两次,一次是RecyclerView在onMeasure时,第二次是在onLayout时调用;在这个方法中我们可以初始化item的摆放位置。

摆放完成后需要重写滑动方法scrollVerticallyBy

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        detachAndScrapAttachedViews(recycler);
        //实际滑动距离  dx
        int trval = dy;
        //如果滑动到最顶部  往下滑   verticalScrollOffset   -
        //第一个坐标值 减 以前最后一个坐标值  //记死
        if (verticalScrollOffset + dy < 0) {
            trval = -verticalScrollOffset;
        } else if (verticalScrollOffset + dy > totalHeight - getHeight()) {
            //如果滑动到最底部  往上滑   verticalScrollOffset   +
            trval = totalHeight - getHeight() - verticalScrollOffset;
        }

        //边界值判断
        verticalScrollOffset += trval;

        //平移容器内的item
        offsetChildrenVertical(trval);
        recyclerViewFillView(recycler, state);
        return trval;
    }

最后还要开启滚动方法:

    @Override
    public boolean canScrollVertically() {
        return true;
    }

完整的自定义LayoutManager:

public class AutoLayoutManager extends RecyclerView.LayoutManager {
    //保存所有item偏移量信息
    //所有数据高度和
    private int totalHeight = 0;
    private SparseArray allItemframs = new SparseArray<>();

    /**
     * 滑动偏移量
     * 如果是正的就是在向上滑,展现上面的view
     * 如果是负的向下
     */
    private int verticalScrollOffset = 0;

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

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        //摆放
        if (getItemCount() <= 0) {
            return;
        }
        //preLayout主要支持动画,直接跳过
        if (state.isPreLayout()) {
            return;
        }
        //将视图分离放入scrap缓存中,以准备重新对view进行排版
        detachAndScrapAttachedViews(recycler);

        int offsetY = 0;
        int offsetX = 0;
        int viewH = 0;
        for (int i = 0; i < getItemCount(); i++) {
            View view = recycler.getViewForPosition(i);
            //需要将当前view添加后再进行侧量宽高
            addView(view);
            measureChildWithMargins(view, 0, 0);

            //系统测量完成宽高后,获取宽高
            int w = getDecoratedMeasuredWidth(view);
            int h = getDecoratedMeasuredHeight(view);
            viewH = h;
            Rect fram = allItemframs.get(i);
            if (fram == null) {
                fram = new Rect();
            }
            //之前累积的宽度加上当前item的宽度>recyclerView的宽度,则需要换行
            if (offsetX + w > getWidth()) {
                //换行的View的值
                offsetY += h;
                offsetX = w;
                fram.set(0, offsetY, w, offsetY + h);
            } else {
                //不需要换行
                fram.set(offsetX, offsetY, offsetX + w, offsetY + h);
                offsetX += w;
            }
            //要针对于当前View   生成对应的Rect  然后放到allItemframs数组
            allItemframs.put(i, fram);
        }
        totalHeight = offsetY + viewH;
        //detach 轻量级的移除操作;remove 重量级的移除操作
        //回收不可见的
        recyclerViewFillView(recycler, state);
    }

    private void recyclerViewFillView(RecyclerView.Recycler recycler, RecyclerView.State state) {
        //清空RecyclerView的子View
        detachAndScrapAttachedViews(recycler);
        Rect phoneFrame = new Rect(0, verticalScrollOffset, getWidth(), verticalScrollOffset + getHeight());
        //将滑出屏幕的view进行回收
        Rect childRect = new Rect();
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            Rect child = allItemframs.get(i);
            if (!Rect.intersects(phoneFrame, child)) {
                removeAndRecycleView(childView, recycler);
            }
        }

        //可见区域出现在屏幕上的子view
        for (int j = 0; j < getItemCount(); j++) {
            if (Rect.intersects(phoneFrame, allItemframs.get(j))) {
                //scrap回收池里面拿的
                View scrap = recycler.getViewForPosition(j);
                measureChildWithMargins(scrap, 0, 0);
                addView(scrap);
                Rect frame = allItemframs.get(j);
                layoutDecorated(scrap, frame.left, frame.top - verticalScrollOffset,
                        frame.right, frame.bottom - verticalScrollOffset);
            }
        }
    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        detachAndScrapAttachedViews(recycler);
        //实际滑动距离  dx
        int trval = dy;
        //如果滑动到最顶部  往下滑   verticalScrollOffset   -
        //第一个坐标值 减 最后一个坐标值  //记死
        if (verticalScrollOffset + dy < 0) {
            trval = -verticalScrollOffset;
        } else if (verticalScrollOffset + dy > totalHeight - getHeight()) {
            //如果滑动到最底部  往上滑   verticalScrollOffset   +
            trval = totalHeight - getHeight() - verticalScrollOffset;
        }

        //边界值判断
        verticalScrollOffset += trval;

        //平移容器内的item
        offsetChildrenVertical(trval);
        recyclerViewFillView(recycler, state);
        return trval;
    }

    @Override
    public boolean canScrollVertically() {
        return true;
    }

}

 

你可能感兴趣的:(自定义view)