(五)RecycleView 动态设置改变列表显示的高度,禁止滑动

推荐阅读 java

(一)RecycleView 初探回收复用,onCreateView和onBindView调用关系ide

(二)Android RecycleView实现吸附小标题的Demo(附源码)布局

(三)RecycleView 自定义下拉刷新,上拉加载监听学习

(四)RecycleView 滑动到置顶、Adapter局部刷新ui

(五)RecycleView 动态设置改变列表显示的高度this


前言

RecycleView 是一个可回收复用的列表控件,也是使用较广泛的。在使用时也会结合业务功能需求作出一些改变。好比两个Recycleview之间有交互,又或者嵌套滑动处理,又或者高度动态设置。本篇正是关于如何动态改变列表的高度。spa

先看效果图:.net


 

1、RecycleView测量原理

RecyclerView.onMeasure() 方法源码,测量顺序以下:code

protected void onMeasure(int widthSpec, int heightSpec) {
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    // 一、是否进入 自动测量自身尺寸
    if (mLayout.mAutoMeasure) {    
        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);   
        if (skipMeasure || mAdapter == null) {
            return;
        }
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
        }
        mLayout.setMeasureSpecs(widthSpec, heightSpec);
        mState.mIsMeasuring = true;
        dispatchLayoutStep2();
 
        // 关键:经过测量孩子view宽高来肯定自身尺寸
        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
        if (mLayout.shouldMeasureTwice()) {
            mLayout.setMeasureSpecs(
                    MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
            mState.mIsMeasuring = true;
            dispatchLayoutStep2();.
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
        }
    } else {
        //二、若是是固定大小,执行会和上面效果同样
        if (mHasFixedSize) {  
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            return;
        }
        // 定制测量
        if (mAdapterUpdateDuringMeasure) {
            eatRequestLayout();
            processAdapterUpdatesAndSetAnimationFlags();
 
            if (mState.mRunPredictiveAnimations) {
                mState.mInPreLayout = true;
            } else {
                // 使用剩余的更新来提供与布局传递一致的状态。
                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);   
        resumeRequestLayout(false);
        mState.mInPreLayout = false; // 清除
    }
}

有两个判断比较显眼:mAutoMeasure、mHasFixedSize。这俩都会让RecycleView自动测量所有孩子的高度,从而能肯定自身尺寸MeasuredDimension大小。对象


 

2、实现方案1:经过重写onMeasure

经过重写 LayoutManage的onMeasure()方法,获取到RecycleView的一个item的viewholder对象实例,若是这个item实例对象存在,就进行测量item的大小,拿到确切的高度Height值后,就能够动态设置Recycleview显示多少个item的高度了。

须要注意,item的布局最好提早设定固定的高度,不然获取为0。

记得要设置mAutoMeasure、mHasFixedSize值为false,不设置可能会报错。

 

LinearLayoutManager mLayoutManager = new LinearLayoutManager(this){
            @Override
            public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
 
                    View view = recycler.getViewForPosition(0);
                    if (view != null) {
                        measureChild(view, widthSpec, heightSpec);
                        int measuredHeight = view.getMeasuredHeight();
 //int measuredWidth = View.MeasureSpec.getSize(widthSpec);
                        int showHeight = measuredHeight * state.getItemCount();
                        if(state.getItemCount() >= 5){
                            showHeight = measuredHeight * 5;
                        }
                        setMeasuredDimension(widthSpec, showHeight);
                    }
            }
    };
 
    mLayoutManager.setAutoMeasureEnabled(false);
    mRecyclerview.setHasFixedSize(false);
    mRecyclerview.setLayoutManager(mLayoutManager);

 

3、实现方案2:经过修改LayoutParams(推荐)

经过adapter传入不一样的viewType拿到ViewHolder对象,对这个ViewHolder进行测量,而后获得测量后的高度值。最后,就能够根据item调整设置列表的布局参数的高度。

须要注意,在NestedScrollView嵌套RecycleView时,在RecycleView彻底展现时(即按itemCount总数),RecycleView仍然会有上下可滑动的小空间,虽然只是一点点,也是会影响用户体验。所以,须要在彻底展开时,将它设置禁止滑动

boolean isOpen ; //记录展开、收起状态
private boolean setFitHeight(RecyclerView recyclerView){
        RecyclerView.Adapter adapter = recyclerView.getAdapter();
        int itemCount = adapter.getItemCount();
        int measuredHeight = 0;
        if (itemCount >0){
            RecyclerView.ViewHolder holder = adapter.createViewHolder(recyclerView, adapter
                    .getItemViewType(0));//经过viewType类型返回ViewHolder
            adapter.onBindViewHolder(holder, 0);
            holder.itemView.measure(
                    View.MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), View.MeasureSpec.EXACTLY),
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
            holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(), holder.itemView.getMeasuredHeight());
            holder.itemView.setDrawingCacheEnabled(true);
            holder.itemView.buildDrawingCache();
            measuredHeight = holder.itemView.getMeasuredHeight();
        }

        if (isOpen){
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    measuredHeight * 3);
            recyclerView.setLayoutParams(layoutParams);
            recyclerView.setNestedScrollingEnabled(true);//容许滑动
            return isOpen = false;
        }else{
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    measuredHeight * itemCount);
            recyclerView.setLayoutParams(layoutParams);
            recyclerView.setNestedScrollingEnabled(false);//禁止滑动
            return isOpen = true;
        }
    }

 

4、总结

上面两种实现方式,都离开View的测量,所以建议你们多深刻学习自定义View流程mesure\layout\draw源码

第一种方案代码简单,使用方便,但扩展性和灵活性不强。适用于该页面静态显示高度,不动态改变。

第二种方案更值得推荐。由于咱们的RecycleView的item会有不一样风格大小的时候,它能够经过viewType获得每一种item高度,从而设置固定高度。另外,RecycleView的布局参数LayoutParams的值改变即响应。


点个赞,加关注。

相关文章
相关标签/搜索