推荐阅读 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的值改变即响应。
点个赞,加关注。