欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-24 15:35:04
...

推荐阅读 

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

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

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

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

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


前言

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

先看效果图:

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


 

一、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的值改变即响应。


点个赞,加关注。