CustomRecycleView:主要用于实现布局,分割线

CustomRecycleView

/**
 * 自定义recycleview,主要用于实现布局,分割线
 */

public class CustomRecycleView extends RecyclerView {

    //流式布局LayoutManager
    private FlowLayoutManager mFlowLayoutManager;

    public CustomRecycleView(Context context) {
        this(context, null);
    }

    public CustomRecycleView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomRecycleView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

    private void init(Context context, @Nullable AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomRecycleView);
        int layoutManagerType = a.getInteger(R.styleable.CustomRecycleView_layoutManagerType, 0);
        int spanCount = a.getInteger(R.styleable.CustomRecycleView_spanCount, 3);
        boolean canScroll = a.getBoolean(R.styleable.CustomRecycleView_canScroll, true);
        boolean divider = a.getBoolean(R.styleable.CustomRecycleView_useDivider, false);
        int dividerColor = a.getColor(R.styleable.CustomRecycleView_dividerColor, -1);
        int dividerHeight = (int) (a.getDimension(R.styleable.CustomRecycleView_dividerHeight, 3));
        int dividerResId = a.getResourceId(R.styleable.CustomRecycleView_dividerResId, -1);
        //int dividerMarginStart = (int)(a.getDimension(R.styleable.CustomRecycleView_dividerResId,0));
        //int dividerMarginEnd = (int)(a.getDimension(R.styleable.CustomRecycleView_dividerResId,0));


        DividerItemDecoration.DividerDec dividerDec = new DividerItemDecoration.DividerDec(
                a.getBoolean(R.styleable.CustomRecycleView_useStartDivider, false),
                a.getBoolean(R.styleable.CustomRecycleView_useTopDivider, false),
                a.getBoolean(R.styleable.CustomRecycleView_useEndDivider, false),
                a.getBoolean(R.styleable.CustomRecycleView_useBottomDivider, false)
        );

        if (layoutManagerType == 3) {
            mFlowLayoutManager = new FlowLayoutManager();
        }

        initLayoutManager(layoutManagerType, canScroll, spanCount);

        initDivider(divider, layoutManagerType, dividerColor, dividerHeight, dividerResId, dividerDec);

        a.recycle();
    }


    private void initLayoutManager(int layoutManagerType, boolean canScroll, int spanCount) {
        setNestedScrollingEnabled(canScroll);
        switch (layoutManagerType) {
            case 0:
                setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false) {

                    @Override
                    public boolean canScrollVertically() {
                        return canScroll && super.canScrollVertically();
                    }

                    @Override
                    public void onLayoutChildren(Recycler recycler, State state) {
                        //解决java.lang.IndexOutOfBoundsException: Inconsistency detected.的bug
                        try {
                            super.onLayoutChildren(recycler, state);
                        } catch (IndexOutOfBoundsException e) {
                            e.printStackTrace();
                        }
                    }
                });
                break;
            case 1:
                setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false) {

                    @Override
                    public boolean canScrollHorizontally() {
                        return canScroll && super.canScrollHorizontally();
                    }

                    @Override
                    public void onLayoutChildren(Recycler recycler, State state) {
                        try {
                            super.onLayoutChildren(recycler, state);
                        } catch (IndexOutOfBoundsException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void smoothScrollToPosition(RecyclerView recyclerView,
                                                       State state, final int position) {

                        LinearSmoothScroller smoothScroller =
                                new LinearSmoothScroller(recyclerView.getContext()) {
                                    // 返回:滑过1px时经历的时间(ms)。
                                    @Override
                                    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                                        return 150f / displayMetrics.densityDpi;
                                    }
                                };

                        smoothScroller.setTargetPosition(position);
                        startSmoothScroll(smoothScroller);
                    }

                });
                break;
            case 2:
                setLayoutManager(new GridLayoutManager(getContext(), spanCount) {

                    @Override
                    public void onLayoutChildren(Recycler recycler, State state) {
                        try {
                            super.onLayoutChildren(recycler, state);
                        } catch (IndexOutOfBoundsException e) {
                            e.printStackTrace();
                        }
                    }
                });
                break;
            case 3:
                setLayoutManager(mFlowLayoutManager);
                break;
            case 4:
                //不设置任何适配器,留给外部自行设置
                break;
        }
    }

    /***
     * 在setLayoutManager后面调用
     * @param divider 是否需要分割线
     * @param layoutManagerType 分割线方向
     */
    private void initDivider(boolean divider, int layoutManagerType, int dividerColor, int dividerHeight, int dividerResId, DividerItemDecoration.DividerDec dividerDec) {
        if (!divider) {
            return;
        }
        DividerItemDecoration dividerItemDecoration = null;
        switch (layoutManagerType) {
            case 0:
                dividerItemDecoration = getDividerItemDecoration(DividerItemDecoration.HORIZONTAL_LIST, dividerColor, dividerHeight, dividerResId, dividerDec);
                break;
            case 1:
                dividerItemDecoration = getDividerItemDecoration(DividerItemDecoration.VERTICAL_LIST, dividerColor, dividerHeight, dividerResId, dividerDec);
                break;
            case 2:
            case 3:
                dividerItemDecoration = getDividerItemDecoration(DividerItemDecoration.BOTH_SET, dividerColor, dividerHeight, dividerResId, dividerDec);
                break;
            default:
                break;
        }

        if (dividerItemDecoration != null) {
            addItemDecoration(dividerItemDecoration);
        }
    }

    private DividerItemDecoration getDividerItemDecoration(int orientation, int dividerColor, int dividerHeight, int dividerResId, DividerItemDecoration.DividerDec dividerDec) {
        DividerItemDecoration dividerItemDecoration;
        if (dividerResId > 0) {
            dividerItemDecoration = new DividerItemDecoration(getContext(), orientation, dividerResId, dividerDec);
        } else if (dividerHeight > 0) {
            dividerItemDecoration = new DividerItemDecoration(getContext(), orientation, dividerHeight, dividerColor, dividerDec);
        } else {
            dividerItemDecoration = new DividerItemDecoration(getContext(), orientation, dividerDec);
        }
        return dividerItemDecoration;
    }


    /***
     * 水平平滑滚动
     */
    public void moveToPosition(int position) {
        if (!(getLayoutManager() instanceof LinearLayoutManager)) {
            return;
        }
        LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
        if (position > -1 && getAdapter() != null && position < getAdapter().getItemCount()) {
            scrollToPosition(position);
            layoutManager.scrollToPositionWithOffset(position, 0);
        }
    }

    /***
     * 还有问题
     * 水平平滑滚动
     * @param targetPos 目标位置
     */
    public boolean moveToPositionH(int targetPos) {
        boolean move = false;
        if (!(getLayoutManager() instanceof LinearLayoutManager)) {
            return move;
        }
        LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();

        //先从RecyclerView的LayoutManager中获取第一项和最后一项的Position
        int firstItem = layoutManager.findFirstVisibleItemPosition();
        int lastItem = layoutManager.findLastVisibleItemPosition();
        //然后区分情况
        if (targetPos <= firstItem) {
            //当要置顶的项在当前显示的第一个项的前面时
            smoothScrollToPosition(targetPos);
        } else if (targetPos <= lastItem) {
            //当要置顶的项已经在屏幕上显示时
            int left = getChildAt(targetPos - firstItem).getLeft();
            smoothScrollBy(left, 0);
        } else {
            //当要置顶的项在当前显示的最后一项的后面时
            smoothScrollToPosition(targetPos);
            //这里这个变量是用在RecyclerView滚动监听里面的
            move = true;
        }
        return move;

    }
}

自定义属性

    <declare-styleable name="CustomRecycleView">
        <attr name="layoutManagerType" format="enum">
            <enum name="linerV" value="0" />
            <enum name="linerH" value="1" />
            <enum name="grid" value="2" />
            <enum name="flow" value="3" />
            <enum name="empty" value="4" />
        </attr>

        <attr name="spanCount" format="integer" />
        <attr name="canScroll" format="boolean" />
        <attr name="useDivider" format="boolean" />
        <attr name="dividerHeight" format="dimension|reference" />
        <attr name="dividerColor" format="color" />
        <attr name="dividerResId" format="reference" />
        <attr name="dividerMarginStart" format="dimension|reference" />
        <attr name="dividerMarginEnd" format="dimension|reference" />
        <attr name="useTopDivider" format="boolean" />
        <attr name="useStartDivider" format="boolean" />
        <attr name="useEndDivider" format="boolean" />
        <attr name="useBottomDivider" format="boolean" />
    </declare-styleable>

DividerItemDecoration :分割线

public class DividerItemDecoration extends RecyclerView.ItemDecoration {
    private Paint mPaint;
    //取名mDivider似乎更恰当
    private Drawable mDrawable;
    //分割线高度,默认为1px
    private int mDividerHeight = 2;
    //列表的方向
    private int mOrientation;
    //系统自带的参数
    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
    //水平
    public static final int HORIZONTAL_LIST = RecyclerView.HORIZONTAL;
    //垂直
    public static final int VERTICAL_LIST = RecyclerView.VERTICAL;
    //水平+垂直
    public static final int BOTH_SET = 2;

    private DividerDec mUseDivDec;

    public static class DividerDec{
        public boolean useTopDiv = false;
        public boolean useStartDiv = false;
        public boolean useEndDiv = false;
        public boolean useBottomDiv = false;

        public DividerDec(boolean useStartDiv, boolean useTopDiv, boolean useEndDiv, boolean useBottomDiv) {
            this.useStartDiv = useStartDiv;
            this.useTopDiv = useTopDiv;
            this.useEndDiv = useEndDiv;
            this.useBottomDiv = useBottomDiv;
        }
    }


    /**
     * 默认分割线:高度为2px,颜色为灰色
     *
     * @param context     上下文
     * @param orientation 列表方向
     */
    public DividerItemDecoration(Context context, int orientation, DividerDec dividerDec) {
        this.setOrientation(orientation);
        //获取xml配置的参数
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        //typedArray.getDrawable(attr)这句是说我们可以通过我们的资源获得资源,使用我们的资源名attr去获得资源id
        //看不懂就用自己写一个分割线的图片吧,方法:ContextCompat.getDrawable(context, drawableId);
        mDrawable = a.getDrawable(0);
        //官方的解释是:回收TypedArray,以便后面重用。在调用这个函数后,你就不能再使用这个TypedArray。
        //在TypedArray后调用recycle主要是为了缓存。
        mUseDivDec = dividerDec;
        a.recycle();
    }

    /**
     * 自定义分割线
     *
     * @param context     上下文
     * @param orientation 列表方向
     * @param drawableId  分割线图片
     */
    public DividerItemDecoration(Context context, int orientation, int drawableId, DividerDec dividerDec) {
        this.setOrientation(orientation);
        //旧的getDrawable方法弃用了,这个是新的
        mDrawable = ContextCompat.getDrawable(context, drawableId);
        mDividerHeight = mDrawable.getIntrinsicHeight();
        mUseDivDec = dividerDec;
    }

    /**
     * 自定义分割线
     *
     * @param context       上下文
     * @param orientation   列表方向
     * @param dividerHeight 分割线高度
     * @param dividerColor  分割线颜色
     */
    public DividerItemDecoration(Context context, int orientation,
                                 int dividerHeight, int dividerColor, DividerDec dividerDec) {
        this.setOrientation(orientation);
        mDividerHeight = dividerHeight;
        //抗锯齿画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(dividerColor);
        //填满颜色
        mPaint.setStyle(Paint.Style.FILL);
        mUseDivDec = dividerDec;
    }

    /**
     * 设置方向
     *
     * @param orientation
     */
    public void setOrientation(int orientation) {
        if (orientation < 0 || orientation > 2)
            throw new IllegalArgumentException("invalid orientation");
        mOrientation = orientation;
    }


    /**
     * 绘制分割线之后,需要留出一个外边框,就是说item之间的间距要换一下
     *
     * @param outRect outRect.set(0, 0, 0, 0);的四个参数理解成margin就好了
     * @param view    视图
     * @param parent  父级view
     * @param state
     */
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        //下面super...代码其实调用的就是那个过时的getItemOffsets,也就是说这个方法体内容也可以通通移到那个过时的getItemOffsets中
        super.getItemOffsets(outRect, view, parent, state);
        //获取layoutParams参数
        RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
        //当前位置
        int itemPosition = layoutParams.getViewLayoutPosition();
        //ItemView数量
        int childCount = parent.getAdapter().getItemCount();
        switch (mOrientation) {
            case BOTH_SET:
                //获取Layout的相关参数
                int spanCount = this.getSpanCount(parent);
                if (isLastRaw(parent, itemPosition, spanCount, childCount)) {
                    // 如果是最后一行,则不需要绘制底部
                    outRect.set(0, 0, mDividerHeight, mUseDivDec.useBottomDiv ? mDividerHeight : 0);
                } else if (isLastColum(parent, itemPosition, spanCount, childCount)) {
                    // 如果是最后一列,则不需要绘制右边
                    outRect.set(0, 0, mUseDivDec.useEndDiv ? mDividerHeight : 0, mDividerHeight);
                } else {
                    if (itemPosition % spanCount == 0) {
                        outRect.set(mUseDivDec.useStartDiv ? mDividerHeight : 0, 0, mDividerHeight / 2, mDividerHeight);
                    } else {
                        outRect.set(mDividerHeight / 2, 0, mDividerHeight / 2, mDividerHeight);
                    }

                }
                break;
            case VERTICAL_LIST:
                childCount -= 1;
                //水平布局右侧留Margin,如果是最后一列,就不要留Margin了
                if(itemPosition == 0 && childCount == 0){
                    //只有一列
                    outRect.set(mUseDivDec.useStartDiv ? mDividerHeight : 0,
                            0,  mUseDivDec.useBottomDiv ? mDividerHeight :0,0);
                }else if(itemPosition == 0){
                    //第一列
                    outRect.set(mUseDivDec.useStartDiv ? mDividerHeight : 0,
                            0, mDividerHeight /2, 0);
                }else if(itemPosition == childCount ){
                    //最后一列
                    outRect.set(mDividerHeight/2,
                            0, mUseDivDec.useEndDiv ? mDividerHeight : 0, 0);
                }else{
                    outRect.set(mDividerHeight/2,
                            0, mDividerHeight/2, 0);
                }
                break;
            case HORIZONTAL_LIST:
                childCount -= 1;
                //垂直布局底部留边,最后一行不留
                if(itemPosition == 0 && childCount == 0){
                    //只有一行
                    outRect.set( 0,
                            mUseDivDec.useTopDiv ? mDividerHeight : 0, 0, ( mUseDivDec.useBottomDiv ? mDividerHeight :0));
                }else if(itemPosition == 0){
                    //第一行
                    outRect.set( 0,
                            mUseDivDec.useTopDiv ? mDividerHeight : 0, 0, mDividerHeight/2);
                }else if(itemPosition == childCount ){
                    //最后一行
                    outRect.set(0,
                            mDividerHeight/2,  0, mUseDivDec.useBottomDiv ? mDividerHeight : 0);
                }else{
                    outRect.set(0,
                            mDividerHeight/2, 0, mDividerHeight/2);
                }
                break;
            default:
                break;
        }
    }

    /**
     * 绘制分割线
     *
     * @param c
     * @param parent
     * @param state
     */
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else if (mOrientation == HORIZONTAL_LIST) {
            drawHorizontal(c, parent);
        } else {
            drawHorizontal(c, parent);
            drawVertical(c, parent);
        }
    }

    /**
     * 绘制横向 item 分割线
     *
     * @param canvas 画布
     * @param parent 父容器
     */
    private void drawHorizontal(Canvas canvas, RecyclerView parent) {
        final int x = parent.getPaddingLeft();
        final int width = parent.getMeasuredWidth() - parent.getPaddingRight();
        //getChildCount()(ViewGroup.getChildCount) 返回的是显示层面上的“所包含的子 View 个数”。
        final int childSize = parent.getChildCount();
        for (int i = 0; i < childSize; i++) {
            final View child = parent.getChildAt(i);
            RecyclerView.LayoutParams layoutParams =
                    (RecyclerView.LayoutParams) child.getLayoutParams();
            //item底部的Y轴坐标+margin值
            final int y = child.getBottom() + layoutParams.bottomMargin;
            final int height = y + mDividerHeight;
            if (mDrawable != null) {
                //setBounds(x,y,width,height); x:组件在容器X轴上的起点 y:组件在容器Y轴上的起点
                // width:组件的长度 height:组件的高度
                mDrawable.setBounds(x, y, width, height);
                mDrawable.draw(canvas);
            }
            if (mPaint != null) {
                canvas.drawRect(x, y, width, height, mPaint);
            }
        }
    }

    /**
     * 绘制纵向 item 分割线
     *
     * @param canvas
     * @param parent
     */
    private void drawVertical(Canvas canvas, RecyclerView parent) {
        final int top = parent.getPaddingTop();
        final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom();
        final int childSize = parent.getChildCount();
        for (int i = 0; i < childSize; i++) {
            final View child = parent.getChildAt(i);
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
            final int left = child.getRight() + layoutParams.rightMargin;
            final int right = left + mDividerHeight;
            if (mDrawable != null) {
                mDrawable.setBounds(left, top, right, bottom);
                mDrawable.draw(canvas);
            }
            if (mPaint != null) {
                canvas.drawRect(left, top, right, bottom, mPaint);
            }
        }
    }


    /**
     * 获取列数
     *
     * @param parent
     * @return
     */
    private int getSpanCount(RecyclerView parent) {
        int spanCount = -1;
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            spanCount = ((StaggeredGridLayoutManager) layoutManager)
                    .getSpanCount();
        }
        return spanCount;
    }


    private boolean isLastColum(RecyclerView parent, int pos, int spanCount,
                                int childCount) {
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            int orientation = ((GridLayoutManager) layoutManager)
                    .getOrientation();
            if (orientation == StaggeredGridLayoutManager.VERTICAL) {
                // 如果是最后一列,则不需要绘制右边
                if ((pos + 1) % spanCount == 0)
                    return true;
            } else {
                childCount = childCount - childCount % spanCount;
                // 如果是最后一列,则不需要绘制右边
                if (pos >= childCount)
                    return true;
            }
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            int orientation = ((StaggeredGridLayoutManager) layoutManager)
                    .getOrientation();
            if (orientation == StaggeredGridLayoutManager.VERTICAL) {
                // 如果是最后一列,则不需要绘制右边
                if ((pos + 1) % spanCount == 0)
                    return true;
            } else {
                childCount = childCount - childCount % spanCount;
                // 如果是最后一列,则不需要绘制右边
                if (pos >= childCount)
                    return true;
            }
        }
        return false;
    }

    private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,
                              int childCount) {
        int orientation;
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            childCount = childCount - childCount % spanCount;
            orientation = ((GridLayoutManager) layoutManager)
                    .getOrientation();
            if (orientation == StaggeredGridLayoutManager.VERTICAL) {
                // 如果是最后一行,则不需要绘制底部
                childCount = childCount - childCount % spanCount;
                if (pos >= childCount)
                    return true;
            } else {// StaggeredGridLayoutManager 横向滚动
                // 如果是最后一行,则不需要绘制底部
                if ((pos + 1) % spanCount == 0)
                    return true;
            }
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            orientation = ((StaggeredGridLayoutManager) layoutManager)
                    .getOrientation();
            if (orientation == StaggeredGridLayoutManager.VERTICAL) {
                // 如果是最后一行,则不需要绘制底部
                childCount = childCount - childCount % spanCount;
                if (pos >= childCount)
                    return true;
            } else {// StaggeredGridLayoutManager 横向滚动
                // 如果是最后一行,则不需要绘制底部
                if ((pos + 1) % spanCount == 0)
                    return true;
            }
        }
        return false;
    }
}

FlowLayoutManager :流式布局管理器

public class FlowLayoutManager extends RecyclerView.LayoutManager {

    private static final String TAG = FlowLayoutManager.class.getSimpleName();
    final FlowLayoutManager self = this;

    protected int width, height;
    private int left, top, right;
    //最大容器的宽度
    private int usedMaxWidth;
    //竖直方向上的偏移量
    private int verticalScrollOffset = 0;

    public int getTotalHeight() {
        return totalHeight;
    }

    //计算显示的内容的高度
    protected int totalHeight = 0;
    private Row row = new Row();
    private List<Row> lineRows = new ArrayList<>();

    //保存所有的Item的上下左右的偏移量信息
    private SparseArray<Rect> allItemFrames = new SparseArray<>();

    public FlowLayoutManager() {
    }

    //设置主动测量规则,适应recyclerView高度为wrap_content
    @Override
    public boolean isAutoMeasureEnabled() {
        return true;
    }

    public int getRowCounts() {
        return lineRows.size();
    }

    //每个item的定义
    public class Item {
        int useHeight;
        View view;

        public void setRect(Rect rect) {
            this.rect = rect;
        }

        Rect rect;

        public Item(int useHeight, View view, Rect rect) {
            this.useHeight = useHeight;
            this.view = view;
            this.rect = rect;
        }
    }

    //行信息的定义
    public class Row {
        public void setCuTop(float cuTop) {
            this.cuTop = cuTop;
        }

        public void setMaxHeight(float maxHeight) {
            this.maxHeight = maxHeight;
        }

        //每一行的头部坐标
        float cuTop;
        //每一行需要占据的最大高度
        float maxHeight;
        //每一行存储的item
        List<Item> views = new ArrayList<>();

        public void addViews(Item view) {
            views.add(view);
        }
    }

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

    //该方法主要用来获取每一个item在屏幕上占据的位置
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        Log.d(TAG, "onLayoutChildren");
        totalHeight = 0;
        int cuLineTop = top;
        //当前行使用的宽度
        int cuLineWidth = 0;
        int itemLeft;
        int itemTop;
        int maxHeightItem = 0;
        row = new Row();
        lineRows.clear();
        allItemFrames.clear();
        removeAllViews();
        if (getItemCount() == 0) {
            detachAndScrapAttachedViews(recycler);
            verticalScrollOffset = 0;
            return;
        }
        if (getChildCount() == 0 && state.isPreLayout()) {
            return;
        }
        //onLayoutChildren方法在RecyclerView 初始化时 会执行两遍
        detachAndScrapAttachedViews(recycler);
        if (getChildCount() == 0) {
            width = getWidth();
            height = getHeight();
            left = getPaddingLeft();
            right = getPaddingRight();
            top = getPaddingTop();
            usedMaxWidth = width - left - right;
        }

        for (int i = 0; i < getItemCount(); i++) {
            Log.d(TAG, "index:" + i);
            View childAt = recycler.getViewForPosition(i);
            if (View.GONE == childAt.getVisibility()) {
                continue;
            }
            measureChildWithMargins(childAt, 0, 0);
            int childWidth = getDecoratedMeasuredWidth(childAt);
            int childHeight = getDecoratedMeasuredHeight(childAt);
            int childUseWidth = childWidth;
            int childUseHeight = childHeight;
            //如果加上当前的item还小于最大的宽度的话
            if (cuLineWidth + childUseWidth <= usedMaxWidth) {
                itemLeft = left + cuLineWidth;
                itemTop = cuLineTop;
                Rect frame = allItemFrames.get(i);
                if (frame == null) {
                    frame = new Rect();
                }
                frame.set(itemLeft, itemTop, itemLeft + childWidth, itemTop + childHeight);
                allItemFrames.put(i, frame);
                cuLineWidth += childUseWidth;
                maxHeightItem = Math.max(maxHeightItem, childUseHeight);
                row.addViews(new Item(childUseHeight, childAt, frame));
                row.setCuTop(cuLineTop);
                row.setMaxHeight(maxHeightItem);
            } else {
                //换行
                formatAboveRow();
                cuLineTop += maxHeightItem;
                totalHeight += maxHeightItem;
                itemTop = cuLineTop;
                itemLeft = left;
                Rect frame = allItemFrames.get(i);
                if (frame == null) {
                    frame = new Rect();
                }
                frame.set(itemLeft, itemTop, itemLeft + childWidth, itemTop + childHeight);
                allItemFrames.put(i, frame);
                cuLineWidth = childUseWidth;
                maxHeightItem = childUseHeight;
                row.addViews(new Item(childUseHeight, childAt, frame));
                row.setCuTop(cuLineTop);
                row.setMaxHeight(maxHeightItem);
            }
            //不要忘了最后一行进行刷新下布局
            if (i == getItemCount() - 1) {
                formatAboveRow();
                totalHeight += maxHeightItem;
            }

        }
        totalHeight = Math.max(totalHeight, getVerticalSpace());
        Log.d(TAG, "onLayoutChildren totalHeight:" + totalHeight);
        fillLayout(recycler, state);
    }

    //对出现在屏幕上的item进行展示,超出屏幕的item回收到缓存中
    private void fillLayout(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (state.isPreLayout() || getItemCount() == 0) { // 跳过preLayout,preLayout主要用于支持动画
            return;
        }

        // 当前scroll offset状态下的显示区域
        Rect displayFrame = new Rect(getPaddingLeft(), getPaddingTop() + verticalScrollOffset,
                getWidth() - getPaddingRight(), verticalScrollOffset + (getHeight() - getPaddingBottom()));

        //对所有的行信息进行遍历
        for (int j = 0; j < lineRows.size(); j++) {
            Row row = lineRows.get(j);
            float lineTop = row.cuTop;
            float lineBottom = lineTop + row.maxHeight;
            //如果该行在屏幕中,进行放置item
            //            if (lineTop < displayFrame.bottom && displayFrame.top < lineBottom) {
            List<Item> views = row.views;
            for (int i = 0; i < views.size(); i++) {
                View scrap = views.get(i).view;
                measureChildWithMargins(scrap, 0, 0);
                addView(scrap);
                Rect frame = views.get(i).rect;
                //将这个item布局出来
                layoutDecoratedWithMargins(scrap,
                        frame.left,
                        frame.top - verticalScrollOffset,
                        frame.right,
                        frame.bottom - verticalScrollOffset);
            }
            //            } else {
            //                //将不在屏幕中的item放到缓存中
            //                List<Item> views = row.views;
            //                for (int i = 0; i < views.size(); i++) {
            //                    View scrap = views.get(i).view;
            //                    removeAndRecycleView(scrap, recycler);
            //                }
            //            }
        }
    }

    /**
     * 计算每一行没有居中的viewgroup,让居中显示
     */
    private void formatAboveRow() {
        List<Item> views = row.views;
        for (int i = 0; i < views.size(); i++) {
            Item item = views.get(i);
            View view = item.view;
            int position = getPosition(view);
            //如果该item的位置不在该行中间位置的话,进行重新放置
            if (allItemFrames.get(position).top < row.cuTop + (row.maxHeight - views.get(i).useHeight) / 2) {
                Rect frame = allItemFrames.get(position);
                if (frame == null) {
                    frame = new Rect();
                }
                frame.set(allItemFrames.get(position).left, (int) (row.cuTop + (row.maxHeight - views.get(i).useHeight) / 2),
                        allItemFrames.get(position).right, (int) (row.cuTop + (row.maxHeight - views.get(i).useHeight) / 2 + getDecoratedMeasuredHeight(view)));
                allItemFrames.put(position, frame);
                item.setRect(frame);
                views.set(i, item);
            }
        }
        row.views = views;
        lineRows.add(row);
        row = new Row();
    }

    /**
     * 竖直方向需要滑动的条件
     *
     * @return
     */
    @Override
    public boolean canScrollVertically() {
        return true;
    }

    //监听竖直方向滑动的偏移量
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
                                  RecyclerView.State state) {

        Log.d("TAG", "totalHeight:" + totalHeight);
        //实际要滑动的距离
        int travel = dy;

        //如果滑动到最顶部
        if (verticalScrollOffset + dy < 0) {//限制滑动到顶部之后,不让继续向上滑动了
            travel = -verticalScrollOffset;//verticalScrollOffset=0
        } else if (verticalScrollOffset + dy > totalHeight - getVerticalSpace()) {//如果滑动到最底部
            travel = totalHeight - getVerticalSpace() - verticalScrollOffset;//verticalScrollOffset=totalHeight - getVerticalSpace()
        }

        //将竖直方向的偏移量+travel
        verticalScrollOffset += travel;

        // 平移容器内的item
        offsetChildrenVertical(-travel);
        fillLayout(recycler, state);
        return travel;
    }

    private int getVerticalSpace() {
        return self.getHeight() - self.getPaddingBottom() - self.getPaddingTop();
    }

    public int getHorizontalSpace() {
        return self.getWidth() - self.getPaddingLeft() - self.getPaddingRight();
    }

}

使用LinearLayoutManager

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".rv_custom.CustomRecycleViewActivity">

    <com.zly.mvvmhabit_demo.widget.CustomRecycleView
        android:id="@+id/crv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:dividerColor="#eeeeee"
        app:dividerHeight="0.5dp"
        app:layoutManagerType="linerV"
        app:useDivider="true" />

</LinearLayout>
public class CustomRecycleViewActivity extends AppCompatActivity {

    private CustomRecycleView mCustomRecycleView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_custom_recyclerview);

        mCustomRecycleView = (CustomRecycleView) findViewById(R.id.crv);

        List<String> datas = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            datas.add("赵丽颖" + i);
        }

        mCustomRecycleView.setAdapter(new CommomRvAdapter<String>(this, datas, R.layout.item_common_rv) {
            @Override
            protected void fillData(CommomRvViewHolder holder, int position, String s) {
                holder.setText(R.id.tv, s);
            }
        });
    }
}

使用FlowLayoutManager

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".rv_custom.CustomRvFlowLayoutActivity">


    <com.zly.mvvmhabit_demo.widget.CustomRecycleView
        android:id="@+id/crv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:paddingStart="10dp"
        android:paddingEnd="10dp"
        app:dividerColor="#00000000"
        app:dividerHeight="10dp"
        app:layoutManagerType="flow"
        app:useDivider="true" />

</LinearLayout>

public class CustomRvFlowLayoutActivity extends AppCompatActivity {

    private CustomRecycleView mCustomRecycleView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_custom_rv_flow);

        mCustomRecycleView = (CustomRecycleView) findViewById(R.id.crv);

        List<String> datas = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            datas.add("赵丽颖" + i);
        }

        mCustomRecycleView.setAdapter(new CommomRvAdapter<String>(this, datas, R.layout.item_common_rv_flow) {
            @Override
            protected void fillData(CommomRvViewHolder holder, int position, String s) {
                holder.setText(R.id.tv_lable, s);
            }
        });
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容