ทำความเข้าใจกับ RecyclerView setHasFixedSize


141

setHasFixedSize()ฉันมีบางอย่างที่เข้าใจปัญหา ฉันรู้ว่ามันถูกใช้เพื่อการเพิ่มประสิทธิภาพเมื่อขนาดของRecyclerViewไม่เปลี่ยนแปลงจากเอกสาร

นั่นหมายความว่าอย่างไร? ในกรณีส่วนใหญ่ListViewมักจะมีขนาดคงที่ ในกรณีใดบ้างที่จะไม่เป็นขนาดคงที่? หมายความว่าอสังหาริมทรัพย์ที่มีอยู่บนหน้าจอเติบโตขึ้นพร้อมกับเนื้อหาหรือไม่?



ฉันพบว่าคำตอบนี้มีประโยชน์และเข้าใจง่ายมาก [StackOverflow - rv.setHasFixedSize (true); ] ( stackoverflow.com/questions/28827597/… )
มุสตาฟาฮาซัน

คำตอบ:


117

มากรุ่นที่เรียบง่ายของ RecyclerView มี:

void onItemsInsertedOrRemoved() {
   if (hasFixedSize) layoutChildren();
   else requestLayout();
}

ลิงก์นี้อธิบายสาเหตุที่การโทรrequestLayoutอาจมีราคาแพง โดยทั่วไปเมื่อใดก็ตามที่รายการถูกแทรกย้ายหรือลบขนาด (ความกว้างและความสูง) ของ RecyclerView อาจเปลี่ยนแปลงและในทางกลับกันขนาดของมุมมองอื่น ๆ ในลำดับชั้นมุมมองอาจเปลี่ยนไป นี่เป็นปัญหาอย่างยิ่งหากมีการเพิ่มหรือลบรายการบ่อยๆ

หลีกเลี่ยงเลย์เอาต์ที่ไม่จำเป็นโดยการตั้งค่าsetHasFixedSizeเป็นจริงเมื่อเปลี่ยนเนื้อหาของอะแดปเตอร์ไม่ได้เปลี่ยนความสูงหรือความกว้าง


อัปเดต: JavaDoc ได้รับการอัปเดตเพื่ออธิบายสิ่งที่วิธีการนี้ทำได้ดีขึ้น

RecyclerView สามารถทำการปรับให้เหมาะสมได้หลายอย่างหากสามารถทราบล่วงหน้าว่าขนาดของ RecyclerView ไม่ได้รับผลกระทบจากเนื้อหาอะแด็ปเตอร์ RecyclerView ยังคงสามารถเปลี่ยนขนาดได้ตามปัจจัยอื่น ๆ (เช่นขนาดของพาเรนต์) แต่การคำนวณขนาดนี้ไม่สามารถขึ้นอยู่กับขนาดของลูกหรือเนื้อหาของอะแด็ปเตอร์ได้ (ยกเว้นจำนวนรายการในอะแด็ปเตอร์)

หากคุณใช้ RecyclerView อยู่ในหมวดหมู่นี้ให้ตั้งค่านี้เป็น {@code true} จะช่วยให้ RecyclerView หลีกเลี่ยงการทำให้โครงร่างทั้งหมดเป็นโมฆะเมื่อเนื้อหาของอะแดปเตอร์เปลี่ยน

@param hasFixedSize true หากการเปลี่ยนแปลงอะแด็ปเตอร์ไม่สามารถส่งผลต่อขนาดของ RecyclerView


173
ขนาด RecyclerView จะเปลี่ยนไปทุกครั้งที่คุณเพิ่มสิ่งต่างๆ สิ่งที่ setHasFixedSize ทำคือทำให้แน่ใจว่า (โดยผู้ใช้ป้อนข้อมูล) ว่าการเปลี่ยนแปลงขนาดของ RecyclerView นี้คงที่ ความสูง (หรือความกว้าง) ของรายการจะไม่เปลี่ยนแปลง ทุกรายการที่เพิ่มหรือลบจะเหมือนกัน หากคุณไม่ได้ตั้งค่านี้จะตรวจสอบว่าขนาดของรายการมีการเปลี่ยนแปลงหรือไม่และมีราคาแพง แค่ชี้แจงเพราะคำตอบนี้งง
Arnold Balliu

10
@ArnoldB คำชี้แจงที่ยอดเยี่ยม ฉันจะเถียงมันเป็นคำตอบแบบสแตนด์อโลนด้วยซ้ำ
young_souvlaki

4
@ArnoldB - ผมยังงง คุณแนะนำว่าเราควรตั้งค่า hasFixedSize เป็น true หรือไม่หากความกว้าง / ความสูงของลูกทั้งหมดคงที่ ถ้าใช่จะเกิดอะไรขึ้นถ้ามีความเป็นไปได้ที่เด็กบางคนสามารถลบออกได้ในขณะรันไทม์ (ฉันมีคุณสมบัติการปัดเพื่อปิด) - การตั้งค่าเป็นจริงได้หรือไม่
Jaguar

1
ใช่. เนื่องจากความกว้างและความสูงของรายการไม่เปลี่ยนแปลง เป็นเพียงการเพิ่มหรือลบออก การเพิ่มหรือลบรายการจะไม่เปลี่ยนขนาด
Arnold Balliu

3
@ArnoldB ฉันไม่คิดว่าขนาด (กว้าง / สูง) ของรายการจะเป็นปัญหาที่นี่ จะไม่ตรวจสอบขนาดของรายการด้วย เพียงแค่บอกให้ RecyclerView เรียกrequestLayoutหรือไม่หลังจากอัปเดต dataSet แล้ว
Kimi Chiu

25

สามารถยืนยันได้ว่าsetHasFixedSizeเกี่ยวข้องกับ RecyclerView เองและไม่ใช่ขนาดของแต่ละรายการที่ปรับให้เข้ากับมัน

ตอนนี้คุณสามารถใช้android:layout_height="wrap_content"กับ RecyclerView ซึ่งช่วยให้ CollapsingToolbarLayout ทราบว่าไม่ควรยุบเมื่อ RecyclerView ว่างเปล่า ใช้งานได้เฉพาะเมื่อคุณใช้setHasFixedSize(false)กับ RecylcerView

หากคุณใช้setHasFixedSize(true)บน RecyclerView ลักษณะการทำงานนี้เพื่อป้องกันไม่ให้ CollapsingToolbarLayout ยุบไม่ทำงานแม้ว่า RecyclerView จะว่างเปล่าก็ตาม

หากsetHasFixedSizeเกี่ยวข้องกับขนาดของรายการก็ไม่ควรมีผลใด ๆ เมื่อ RecyclerView ไม่มีรายการ


4
ฉันเพิ่งมีประสบการณ์ที่ชี้ไปในทิศทางเดียวกัน การใช้ RecyclerView กับ GridLayoutManager (3 รายการต่อแถว) และ layout_height = wrap_content เมื่อฉันคลิกปุ่มที่เพิ่มรายการใหม่ 3 รายการในรายการมุมมองผู้รีไซเคิลจะไม่ขยายให้พอดีกับรายการใหม่ แต่จะมีขนาดเท่าเดิมและวิธีเดียวที่จะเห็นรายการใหม่คือการเลื่อนมัน แม้ว่ารายการจะมีขนาดเท่ากัน แต่ฉันก็ต้องเอาออกsetHasFixedSize(true)เพื่อขยายเมื่อมีการเพิ่มรายการใหม่
Mateus Gondim

ฉันคิดว่าคุณพูดถูก จากเอกสารhasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView.ดังนั้นแม้ว่าขนาดของรายการจะเปลี่ยนไปคุณก็ยังตั้งค่านี้เป็นจริงได้
Kimi Chiu

15

ListView มีฟังก์ชันที่มีชื่อคล้ายกันซึ่งฉันคิดว่ามันสะท้อนข้อมูลเกี่ยวกับขนาดของความสูงของรายการแต่ละรายการ เอกสารประกอบสำหรับ RecyclerView ระบุไว้อย่างชัดเจนว่าหมายถึงขนาดของ RecyclerView ไม่ใช่ขนาดของรายการ

จากคอมเมนต์แหล่งที่มา RecyclerView เหนือเมธอด setHasFixedSize ():

 * RecyclerView can perform several optimizations if it can know in advance that changes in
 * adapter content cannot change the size of the RecyclerView itself.
 * If your use of RecyclerView falls into this category, set this to true.

16
แต่ "ขนาด" ของ RecyclerView กำหนดไว้อย่างไร? เป็นขนาดที่มองเห็นได้บนหน้าจอเท่านั้นหรือขนาดเต็มของ RecyclerView ซึ่งเท่ากับ (ผลรวมของความสูงของรายการ + ช่องว่างภายใน + ระยะห่าง)
Vicky Chijwani

2
อันที่จริงสิ่งนี้ต้องการข้อมูลเพิ่มเติม หากคุณลบรายการและมุมมองรีไซเคิลจะถือว่ามีการเปลี่ยนแปลงขนาดหรือไม่
Henrique de Sousa

4
ฉันจะคิดว่า TextView สามารถจัดวางเองได้อย่างไร หากคุณระบุ wrap_content เมื่อคุณตั้งค่าข้อความ TextView จะสามารถขอพาสเลย์เอาท์และเปลี่ยนจำนวนพื้นที่ว่างบนหน้าจอได้ หากคุณระบุ match_parent หรือมิติข้อมูลคงที่ TextView จะไม่ขอผ่านเค้าโครงเนื่องจากขนาดได้รับการแก้ไขและจำนวนของข้อความที่แทรกจะไม่เปลี่ยนจำนวนพื้นที่ที่ครอบครอง RecyclerView ก็เช่นเดียวกัน setHasFixedSize () บอกเป็นนัยถึง RV ไม่จำเป็นต้องขอเค้าโครงผ่านตามการเปลี่ยนแปลงรายการอะแดปเตอร์
dangVarmit

1
@dangVarmit คำอธิบายที่ดี!
howerknea

8

ถ้าเรามีRecyclerViewกับmatch_parentเป็นความสูง / ความกว้างของเราควรเพิ่มsetHasFixedSize(true)ตั้งแต่ขนาดของRecyclerViewตัวเองไม่ได้เปลี่ยนใส่หรือลบรายการเป็นมัน

setHasFixedSizeควรเป็นเท็จถ้าเรามี RecyclerView กับwrap_contentเป็นความสูง / ความกว้างตั้งแต่องค์ประกอบแต่ละเขียนโดยอะแดปเตอร์สามารถเปลี่ยนขนาดของRecyclerขึ้นอยู่กับรายการที่แทรก / ลบดังนั้นขนาดของRecyclerจะแตกต่างกันในแต่ละครั้งที่เราเพิ่ม / ลบ รายการ

เพื่อให้ชัดเจนยิ่งขึ้นถ้าเราใช้

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

เราสามารถใช้ 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"/>

เราควรใช้my_recycler_view.setHasFixedSize(false)สิ่งนี้ใช้ได้หากเราใช้wrap_contentเป็นความกว้างด้วย


6

Wen เราตั้งsetHasFixedSize(true)อยู่บนRecyclerViewหมายถึงว่าขนาดของรีไซเคิลได้รับการแก้ไขและไม่ได้รับผลกระทบจากเนื้อหาอะแดปเตอร์ และในกรณีนี้onLayoutจะไม่ถูกเรียกใช้กับผู้รีไซเคิลเมื่อเราอัปเดตข้อมูลของอะแดปเตอร์ (แต่มีข้อยกเว้น)

ไปที่ตัวอย่าง:

RecyclerViewมีRecyclerViewDataObserver( ค้นหาการปรับใช้เริ่มต้นในไฟล์นี้ ) ด้วยวิธีการต่างๆที่สำคัญคือ:

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

วิธีการนี้เรียกว่าถ้าเราตั้งและอัปเดตข้อมูลอะแดปเตอร์ผ่าน:setHasFixedSize(true) notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMovedในกรณีนี้ไม่มีการเรียกไปยังผู้รีไซเคิลonLayoutแต่มีการเรียกร้องให้requestLayoutอัปเดตชายด์

แต่ถ้าเราตั้งsetHasFixedSize(true)และอัปเดตข้อมูลอะแดปเตอร์ผ่านnotifyItemChangedแล้วมีการเรียกร้องให้onChangeมีการเริ่มต้นรีไซเคิลของและไม่มีการโทรไปยังRecyclerViewDataObserver triggerUpdateProcessorในกรณีนี้รีไซเคิลonLayoutเรียกว่าเมื่อใดก็ตามที่เราตั้งหรือsetHasFixedSize truefalse

// 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();
    }
}

วิธีตรวจสอบด้วยตัวเอง:

สร้างแบบกำหนดเองRecyclerViewและแทนที่:

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)
}

ตั้งค่าขนาดรีไซเคิลเป็นmatch_parent(เป็น xml) พยายามที่จะปรับปรุงข้อมูลอะแดปเตอร์ใช้replaceDataและreplaceOne มี seting แล้วsetHasFixedSize(true)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)
}

และตรวจสอบบันทึกของคุณ

บันทึกของฉัน:

// 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

สรุป:

หากเราตั้งค่าsetHasFixedSize(true)และอัปเดตข้อมูลของอแด็ปเตอร์โดยแจ้งผู้สังเกตการณ์ด้วยวิธีอื่นที่ไม่ใช่การโทรแสดงnotifyDataSetChangedว่าคุณมีประสิทธิภาพเนื่องจากไม่มีการเรียกใช้onLayoutเมธอดรีไซเคิล


คุณทดสอบด้วย RecyclerView ของความสูงโดยใช้ wrap_content หรือ match_parent หรือไม่?
Lubos Mudrak

3

setHasFixedSize (true) หมายถึง RecyclerView มีลูก (รายการ) ที่มีความกว้างและความสูงคงที่ สิ่งนี้ช่วยให้ RecyclerView สามารถปรับให้เหมาะสมได้ดีขึ้นโดยการหาความสูงและความกว้างที่แน่นอนของรายการทั้งหมดตามอะแดปเตอร์


5
นั่นไม่ใช่สิ่งที่ @dangVarmit แนะนำ
แปลกประหลาด

5
ทำให้เข้าใจผิดว่าเป็นขนาดมุมมองของ Recycler ไม่ใช่ขนาดของเนื้อหา
Benoit

0

จะมีผลต่อภาพเคลื่อนไหวของมุมมองรีไซเคิลหากเป็นfalse.. ภาพเคลื่อนไหวแทรกและลบจะไม่แสดง ดังนั้นโปรดตรวจสอบให้แน่ใจว่าtrueในกรณีที่คุณเพิ่มภาพเคลื่อนไหวสำหรับมุมมองรีไซเคิล


0

ถ้าขนาดของ RecyclerView (ตัว RecyclerView เอง)

... ไม่ขึ้นอยู่กับเนื้อหาของอะแดปเตอร์:

mRecyclerView.setHasFixedSize(true);

... ขึ้นอยู่กับเนื้อหาของอะแดปเตอร์:

mRecyclerView.setHasFixedSize(false);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.