最近有这样的一个需求,就是条目铺满整个屏幕,1-3-5的时候单列铺满整个屏幕,6+ 以上的时候两列,

采用的是一下   方法 0:

//RecyclerView大小固定的情况下,根据RecyclerView的宽高设置ItemView的宽高,以达到recyclerview
//刚好显示N行/列数据的目的: 
//在原理是先计算出RecyclerView的宽高,然后在Adapter的onCreateViewHolder中设置view的高度: 
//比如一个垂直列表,希望recyclerview刚好显示三行,这样写:
 
@Override
public TestAdapter.TestItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
    view.getLayoutParams().height = mRecyclerViewHeight/3;
    return new TestItemViewHolder (view);

这种比较简单,还有一些其他方法,可以一起记录一下:

关于RecyclerView的宽高调整

  • 设置ItemView的间隔高宽

        重写ItemDecoration的getItemOffsets函数即可:

recycleview.addItemDecoration(new RecyclerView.ItemDecoration() {
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.set(4, 4, 4, 4);//设置itemView中内容相对边框左,上,右,下距离
    }
});
  • itemView 适应recyclerview

        (见方法 0)

  • recyclerview适应ItemView

ItemView大小固定的情况下,根据ItemView的宽高调整recyclerview的宽高,以达到recyclerview刚好显示N行/列数据的目的:
原理是重写LayoutManager的onMeasure方法,计算itemview的宽高,进而计算出recyclerview的宽高。
比如,一个垂直列表,希望recyclerview刚好显示三行,这样写:

recyclerView.setLayoutManager(new LinearLayoutManager(this) {
    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
        if (getChildCount() > 0) {
            View firstChildView = recycler.getViewForPosition(0);
            measureChild(firstChildView, widthSpec, heightSpec);
            setMeasuredDimension(View.MeasureSpec.getSize(widthSpec), firstChildView.getMeasuredHeight()*3);
        } else {
            super.onMeasure(recycler, state, widthSpec, heightSpec);
        }
    }
});

这种不符合我们的需求,但也是一种思路.

自定义LayoutManager

首先,需要自定义一个 LinearLayoutManager,这里 RecyclerView 在 onMeasure 回调中会调用 LinearLayoutManager 的 onMeasure 方法,所以需要在 LinearLayoutManager 的 onMeasure 中做一些高度设置的处理,大致内容:

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec,int heightSpec) {
    View view = recycler.getViewForPosition(0);
    measureChild(view, widthSpec, heightSpec);
    int measuredWidth = View.MeasureSpec.getSize(widthSpec);
    int measuredHeight = view.getMeasuredHeight();
    setMeasuredDimension(measuredWidth, measuredHeight);
}

两个注意点:这里获取了 view 的高度,也就是 item 布局的高度,所以 item 的布局需要设定固定的高度,否则获取为 0。其次,

mLayoutManager.setAutoMeasureEnabled(false)
mList.setHasFixedSize(false)

这两个设置不能少,否则报错,这里和 RecyclerView.onMeasure 中的调用顺序有关,源码:

protected void onMeasure(int widthSpec, int heightSpec) {
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    if (mLayout.mAutoMeasure) {     //这里为true会进入该分支
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);
        final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                && heightMode == MeasureSpec.EXACTLY;
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);   //在调用mLayout的onMeasure方法时(被自定义复写的方法),mState.mItemCount为0,造成越界异常
        if (skipMeasure || mAdapter == null) {
            return;
        }
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
        }
        // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
        // consistency
        mLayout.setMeasureSpecs(widthSpec, heightSpec);
        mState.mIsMeasuring = true;
        dispatchLayoutStep2();

        // now we can get the width and height from the children.
        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

        // if RecyclerView has non-exact width and height and if there is at least one child
        // which also has non-exact width & height, we have to re-measure.
        if (mLayout.shouldMeasureTwice()) {
            mLayout.setMeasureSpecs(
                    MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
            mState.mIsMeasuring = true;
            dispatchLayoutStep2();
            // now we can get the width and height from the children.
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
        }
    } else {
        if (mHasFixedSize) {   //这里不设置为false会造成与上面相同的问题
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            return;
        }
        // custom onMeasure
        if (mAdapterUpdateDuringMeasure) {
            eatRequestLayout();
            processAdapterUpdatesAndSetAnimationFlags();

            if (mState.mRunPredictiveAnimations) {
                mState.mInPreLayout = true;
            } else {
                // consume remaining updates to provide a consistent state with the layout pass.
                mAdapterHelper.consumeUpdatesInOnePass();
                mState.mInPreLayout = false;
            }
            mAdapterUpdateDuringMeasure = false;
            resumeRequestLayout(false);
        }

        if (mAdapter != null) {
            mState.mItemCount = mAdapter.getItemCount();
        } else {
            mState.mItemCount = 0;
        }
        eatRequestLayout();
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);   //在这里调用时mState.mItemCount才会有值,这与mLayout中获取当前item布局的方式有关:View view = recycler.getViewForPosition(0);
        resumeRequestLayout(false);
        mState.mInPreLayout = false; // clear
    }
}

RecyclerView  用法之Span

使用布局管理器来管理布局。我们继续观察首页布局的图示,我们真的要为了实现这种混合布局自己去写一个布局管理器吗?我们发现上面出现了列表、网格、瀑布流3种交叉混排的混合布局。我们先把瀑布流放在一边,仔细想想如果我们把网格的列数设置为1列,那不就是一个列表布局吗,也就是说我们使用网格布局管理器就可以做出列表的样式,所以说虽然是说用自定义布局管理器,但实际上不需要我们自定义,GridLayoutManager为我们提供了动态改变每个item所占列数的方法:

gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
       @Override
       public int getSpanSize(int position) {
          return gridManager.getSpanCount();
       }
}

getSpanSize方法,返回值就表示当前item占多少列,例如如果我们列数设置的为3列,返回3就表示铺满,也就是和列表一样了。

如图所示,我们给RecyclerView设置一个列数为6的GridLayoutManager,然后再动态地为不同部位的item分别设置SpanSize为6(铺满)、3(1/2)、2(1/3)就行了

android recycleView动态新增item recyclerview动态添加item_宽高

 设置一个列数为6的GridLayoutManager:

recyclerView.setLayoutManager(new GridLayoutManager(recyclerView.getContext(), 6, GridLayoutManager.VERTICAL, false));

在onAttachedToRecyclerView方法中动态为不同position设置不同的SpanSize:

@Override
    public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
 
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if(manager instanceof GridLayoutManager) {
            final GridLayoutManager gridManager = ((GridLayoutManager) manager);
            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    int type = getItemViewType(position);
                    switch (type){
                        case TYPE_SLIDER:
                        case TYPE_TYPE2_HEAD:
                        case TYPE_TYPE3_HEAD:
                            return 6;
                        case TYPE_TYPE2:
                            return 3;
                        case TYPE_TYPE3:
                            return 2;
                        default:
                            return 3;
                    }
                }
            });
        }
    }

我查阅了StaggeredGridLayoutManager,发现它并没有提供动态设置所占列的方法,只是在StaggeredGridLayoutManager.LayoutParams中提供了这样一个方法:

LayoutParams.setFullSpan(true);

当然RecyclerView  用法之Span并不能直接解决我们的问题,但是这也是一种思路,很不错的思路,可以试一下;