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

推荐阅读 

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

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

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

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

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


前言

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

先看效果图:

(五)RecycleView 动态设置改变列表显示的高度,禁止滑动_第1张图片


 

一、RecycleView测量原理

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

protected void onMeasure(int widthSpec, int heightSpec) {
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    // 1、是否进入 自动测量自身尺寸
    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 {
        //2、如果是固定大小,执行会和上面效果一样
        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大小。


 

二、实现方案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);

 

三、实现方案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;
        }
    }

 

四、总结

上面两种实现方式,都离开View的测量,因此建议大家多深入学习自定义View流程mesure\layout\draw源码。

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

第二种方案更值得推荐。因为我们的RecycleView的item会有不同风格大小的时候,它可以通过viewType得到每一种item高度,从而设置固定高度。另外,RecycleView的布局参数LayoutParams的值改变即响应。


点个赞,加关注。

 

 

你可能感兴趣的:(Android,android,Recycle,height,Measure,动态改变)