推薦閱讀 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的值改變即響應。
點個贊,加關注。