(五)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的值改變即響應。


點個贊,加關注。

相關文章
相關標籤/搜索