ถึงแม้ว่า RecyclerView จะเป็น 3rd Party Library ที่ไม่ได้อยู่ใน Android Framework ตั้งแต่แรก แต่ก็เป็นสิ่งที่ขาดไปไม่ได้สำหรับนักพัฒนาแอนดรอยด์ เพราะ RecyclerView ถูกสร้างขึ้นมาเพื่อใช้งานแทน ListView และ GridView

ด้วยจุดเด่นในเรื่องของการ Reusability ที่เรียกว่า View Recycling จึงทำให้ Performance เมื่อแสดงข้อมูลเยอะ ๆ สามารถทำได้ดีกว่าวิธีทั่วไป เพราะ RecyclerView จะแสดง ViewHolder เฉพาะตัวที่อยู่บนหน้าจอเท่านั้น และเมื่อ ViewHolder ตัวไหนไม่ได้ใช้ก็จะเคลียร์ข้อมูลข้างในแล้วเอาไปใช้กับ ViewHolder ตัวถัดไปแทน

จึงทำให้ RecyclerView ถูกประยุกต์ใช้งานในรูปแบบต่าง ๆ นอกเหนือจาก List ทั่วไป เช่น หน้าที่มี View เป็นจำนวนเยอะมากและเรียงยาวต่อกันเป็นแนวตั้ง จึงทำเป็น Multiple View Type เพื่อให้ View แต่ละชุดอยู่ใน ViewHolder แทน เวลาแสดงผลก็จะได้แสดงแค่เฉพาะส่วนที่อยู่บนหน้าเพื่อเพิ่ม Performance ในการแสดงผลของหน้านั้น ๆ

แต่ก็มีวิธีประยุกต์ใช้งานแบบหนึ่งที่ไม่ควรทำ นั่นก็คือการใส่ RecyclerView ไว้ข้างใน NestedScrollView ร่วมกับ View ตัวอื่น

ใช้ NestedScrollView จะได้ไม่ต้องทำ Multiple View Type ใน RecyclerView

เนื่องจากการทำ Multiple View Type จะมี Boilerplate Code ประมาณนึง จึงทำให้นักพัฒาบางคนตัดสินใจสร้าง RecyclerView แบบ Single View Type ขึ้นมา แล้ววางไว้ใน NestedScrollView ร่วมกับ View ตัวอื่นแทน

และนี่ก็คือจุดเริ่มต้นของปัญหาที่บทความนี้จะพูดถึงนั่นเอง

ใส่ RecyclerView ไว้ใน NestedScrollView แล้วไม่ดียังไง?

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

ความสามารถ View Recycle ของ RecyclerView จะหายไป

ทั้งนี้ก็เพราะว่าคุณสมบัติของ NestedScrollView จะทำให้ View ที่อยู่ข้างในมีความสูงเท่ากับ Content ของ View นั้น ๆ ในทันที

ยกตัวอย่างเช่น เจ้าของบล็อกสร้าง NestedScrollView ขึ้นมาโดยมี LinearLayout ที่มี TextView และ Button อยู่ข้างในแบบนี้

<androidx.core.widget.NestedScrollView
    android:id="@+id/scrollView"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="Hello World" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="Button" />
    </LinearLayout>
</androidx.core.widget.NestedScrollView>

จะเห็นว่าเจ้าของบล็อกกำหนดความสูงเป็น Match Parent ไว้ทั้งหมด

เมื่อลองทดสอบดูก็จะพบว่าแทนที่ความสูงของ TextView และ Button จะเท่ากับขนาดของหน้าจอ แต่กลายเป็นว่ามีความสูงเท่ากับข้อความข้างในไม่ต่างอะไรกับ Wrap Content เลย

เพราะการที่ View ข้างในมีความสูงเป็น Match Parent จะทำให้ NestedScrollView ไม่สามารถรู้ได้ว่าตัวมันเองจะ Scroll ได้แค่ไหน ดังนั้น View ที่อยู่ข้างในจึงถูกเปลี่ยนให้มีความสูงเท่ากับ Content ที่อยู่ข้างในแทน ถึงแม้ว่าผู้ที่หลงเข้ามาอ่านจะกำหนดไว้ว่าเป็น Match Parent ก็ตาม

ซึ่ง RecyclerView ก็เป็นแบบนั้นเช่นกัน แต่เนื่องจากเป็น Dynamic List ทำให้ข้อมูลถูกแสดงออกมาทั้งหมดในทันที เพื่อให้ NestedScrollView รู้ความสูงของ RecyclerView ได้

นั่นหมายความว่าถ้ามีของใน RecyclerView เป็นจำนวนเยอะมาก ก็จะทำให้หน้าดังกล่าวมีอาการค้างตอนโหลดครั้งแรก และใช้มี Memory Usage สูงเกินจำเป็นเนื่องจากต้องถือ View ทั้งหมดในหน้านั้น ๆ ไว้ตลอดการใช้งานด้วย

แล้วควรใช้วิธีไหนดี?

เพื่อให้ใช้ RecyclerView ได้อย่างมีประสิทธิภาพมากที่สุดจึงไม่ควรใส่ไว้ใน Scrollable View ใด ๆ และถ้าจำเป็นต้องมี View อื่น ๆ แทรกอยู่ด้วยก็ควรทำ Multiple View Type หรือจะแยก Adapter หลาย ๆ ตัวแล้วใช้ ConcatAdapter ก็ได้เช่นกัน

นั่นก็เพราะว่า RecyclerView มีคุณสมบัติเป็น Scrollable View ไงล่ะ