Understanding RecyclerView setHasFixedSize

后端 未结 8 1910
闹比i
闹比i 2020-11-28 01:52

I\'m having some trouble understanding setHasFixedSize(). I know that it is used for optimization when the size of RecyclerView doesn\'t change, fr

相关标签:
8条回答
  • 2020-11-28 02:34

    If we have a RecyclerView with match_parent as height/width, we should add setHasFixedSize(true) since the size of the RecyclerView itself does not change inserting or deleting items into it.

    setHasFixedSize should be false if we have a RecyclerView with wrap_content as height/width since each element inserted by the adapter could change the size of the Recycler depending on the items inserted/deleted, so, the size of the Recycler will be different each time we add/delete items.

    To be more clear, if we use

    <android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    

    We can use my_recycler_view.setHasFixedSize(true)

    <android.support.v7.widget.RecyclerView
            android:id="@+id/my_recycler_view"
            android:scrollbars="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    

    We should use my_recycler_view.setHasFixedSize(false) , this applies if we also use wrap_content as width

    0 讨论(0)
  • 2020-11-28 02:35

    Wen we set setHasFixedSize(true) on RecyclerView that means recycler's size is fixed and is not affected by the adapter contents. And in this case onLayout is not called on recycler when we update the adaptrer's data (but there is an exception).

    Let's go to the example:

    RecyclerView has a RecyclerViewDataObserver (find default implemntation in this file) with several methods, the main important is:

    void triggerUpdateProcessor() {
        if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
            ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
        } else {
            mAdapterUpdateDuringMeasure = true;
            requestLayout();
        }
    }
    

    This method is called if we set setHasFixedSize(true) and update an adapter's data via: notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved. In this case there is no calls to the recycler's onLayout, but there is calls to requestLayout for updating childs.

    But if we set setHasFixedSize(true) and update an adapter's data via notifyItemChanged then there is call to onChange of the recycler's default RecyclerViewDataObserver and no calls to triggerUpdateProcessor. In this case the recycler onLayout is called whenever we set setHasFixedSize true or false.

    // no calls to triggerUpdateProcessor
    @Override
    public void onChanged() {
        assertNotInLayoutOrScroll(null);
         mState.mStructureChanged = true;
    
         processDataSetCompletelyChanged(true);
         if (!mAdapterHelper.hasPendingUpdates()) {
             requestLayout();
         }
    }
    
    // calls to triggerUpdateProcessor
    @Override
    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
            triggerUpdateProcessor();
        }
    }
    

    How to check by yourself:

    Create custom RecyclerView and override:

    override fun requestLayout() {
        Log.d("CustomRecycler", "requestLayout is called")
        super.requestLayout()
    }
    
    override fun invalidate() {
        Log.d("CustomRecycler", "invalidate is called")
        super.invalidate()
    }
    
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        Log.d("CustomRecycler", "onLayout is called")
        super.onLayout(changed, l, t, r, b)
    }
    

    Set the recycler size to match_parent (in xml). Try to update adapter's data using replaceData and replaceOne with seting setHasFixedSize(true) and then false.

    // onLayout is called every time
    fun replaceAll(data: List<String>) {
        dataSet.clear()
        dataSet.addAll(data)
        this.notifyDataSetChanged()
    }
    
    // onLayout is called only for setHasFixedSize(false)
    fun replaceOne(data: List<String>) {
        dataSet.removeAt(0)
        dataSet.addAll(0, data[0])
        this.notifyItemChanged(0)
    }
    

    And check your log.

    My log:

    // for replaceAll
    D/CustomRecycler: requestLayout is called
    D/CustomRecycler: onMeasure is called
    D/CustomRecycler: onMeasure is called
    D/CustomRecycler: onLayout
    D/CustomRecycler: requestLayout is called
    D/CustomRecycler: requestLayout is called
    D/CustomRecycler: onDraw is called
    
    // for replaceOne
    D/CustomRecycler: requestLayout is called
    D/CustomRecycler: onDraw is called
    D/CustomRecycler: requestLayout is called
    D/CustomRecycler: onDraw is called
    

    Summarize:

    If we set setHasFixedSize(true) and update adapter's data with notifying an observer in some other way than calling notifyDataSetChanged, then you have some perfomance, because the is no calls to the recycler onLayout method.

    0 讨论(0)
提交回复
热议问题