สร้างรายการแบบไดนามิกด้วย RecyclerView   ส่วนหนึ่งของ Android Jetpack

ลองใช้ Compose
Jetpack Compose เป็นชุดเครื่องมือ UI ที่แนะนำสำหรับ Android ดูวิธีทำงานกับเลย์เอาต์ใน Compose

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

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

คลาสที่สำคัญ

โดยมีหลายคลาสที่ทำงานร่วมกันเพื่อสร้างรายการแบบไดนามิก

  • RecyclerView คือViewGroupที่มีมุมมอง ที่สอดคล้องกับข้อมูลของคุณ ซึ่งเป็นมุมมองในตัว คุณจึงเพิ่ม RecyclerView ลงในเลย์เอาต์ได้เหมือนกับเพิ่มองค์ประกอบ UI อื่นๆ

  • องค์ประกอบแต่ละรายการในลิสต์จะกำหนดโดยออบเจ็กต์ view holder เมื่อสร้างตัวยึดมุมมอง จะไม่มีข้อมูลใดๆ ที่เชื่อมโยงอยู่ หลังจากสร้างตัวยึดมุมมองแล้ว RecyclerView จะเชื่อมโยงกับข้อมูล คุณ กำหนด View Holder โดยการขยาย RecyclerView.ViewHolder

  • RecyclerView จะขอมุมมองและเชื่อมโยงมุมมองกับข้อมูล โดยการเรียกใช้เมธอดในอะแดปเตอร์ คุณกำหนดอะแดปเตอร์โดยการขยาย RecyclerView.Adapter

  • ตัวจัดการเลย์เอาต์จะจัดเรียงองค์ประกอบแต่ละรายการในลิสต์ คุณสามารถ ใช้ตัวจัดการเลย์เอาต์ตัวใดตัวหนึ่งที่จัดไว้ให้โดยไลบรารี RecyclerView หรือจะ กำหนดเองก็ได้ LayoutManager ทั้งหมดอิงตามคลาส LayoutManager Abstract ของไลบรารี

คุณดูวิธีประกอบชิ้นส่วนทั้งหมดเข้าด้วยกันได้ในแอปตัวอย่าง RecyclerView (Kotlin) หรือแอปตัวอย่าง RecyclerView (Java)

ขั้นตอนการติดตั้งใช้งาน RecyclerView

หากจะใช้ RecyclerView คุณต้องทำสิ่งต่อไปนี้ ซึ่งจะอธิบายรายละเอียดในส่วนต่อไปนี้

  1. เลือกวิธีแสดงรายการหรือตารางกริด โดยปกติแล้ว คุณสามารถ ใช้เลย์เอาต์แมเนเจอร์มาตรฐานของไลบรารี RecyclerView ได้

  2. ออกแบบลักษณะและการทำงานของแต่ละองค์ประกอบในรายการ ขยายคลาส ViewHolder ตามการออกแบบนี้ ViewHolder เวอร์ชันของคุณมีฟังก์ชันทั้งหมดสำหรับรายการในลิสต์ ตัวยึดมุมมองเป็น Wrapper รอบ View และมุมมองนั้นได้รับการจัดการโดย RecyclerView

  3. กําหนดAdapterที่เชื่อมโยงข้อมูลกับมุมมองViewHolder

นอกจากนี้ยังมีตัวเลือกการปรับแต่งขั้นสูงที่ช่วยให้คุณปรับแต่ง RecyclerView ให้ตรงกับความต้องการของคุณได้

วางแผนเลย์เอาต์

รายการใน RecyclerView จะจัดเรียงตามคลาส LayoutManager ไลบรารี RecyclerView มี LayoutManager 3 แบบที่จัดการ สถานการณ์เลย์เอาต์ที่พบบ่อยที่สุด

  • LinearLayoutManager จัดเรียงรายการในลิสต์แบบ 1 มิติ
  • GridLayoutManager จัดเรียงรายการในตารางแบบ 2 มิติ
    • หากจัดเรียงตารางกริดในแนวตั้ง GridLayoutManager จะพยายามทําให้องค์ประกอบทั้งหมดในแต่ละแถวมีความกว้างและความสูงเท่ากัน แต่แถวต่างๆ อาจมีความสูงแตกต่างกันได้
    • หากจัดเรียงตารางในแนวนอน GridLayoutManager จะพยายามทำให้องค์ประกอบทั้งหมดในแต่ละคอลัมน์มีความกว้างและความสูงเท่ากัน แต่คอลัมน์ต่างๆ อาจมีความกว้างแตกต่างกันได้
  • StaggeredGridLayoutManager คล้ายกับ GridLayoutManager แต่ไม่จำเป็นต้องให้รายการในแถวมีความสูงเท่ากัน (สำหรับตารางกริดแนวตั้ง) หรือรายการในคอลัมน์เดียวกันมีความกว้างเท่ากัน (สำหรับตารางกริดแนวนอน) ผลลัพธ์คือรายการ ในแถวหรือคอลัมน์อาจมีระยะห่างจากกัน

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

ติดตั้งใช้งานอะแดปเตอร์และตัวยึดมุมมอง

เมื่อกำหนดเลย์เอาต์แล้ว คุณจะต้องใช้ Adapter และ ViewHolder คลาสทั้ง 2 นี้จะทำงานร่วมกันเพื่อกำหนดวิธีแสดงข้อมูล ViewHolder เป็น Wrapper รอบ View ที่มีเลย์เอาต์สำหรับแต่ละรายการในลิสต์ Adapter สร้างออบเจ็กต์ ViewHolder ตามต้องการ และยังตั้งค่าข้อมูลสําหรับมุมมองเหล่านั้นด้วย กระบวนการ เชื่อมโยงมุมมองกับข้อมูลเรียกว่าการเชื่อมโยง

เมื่อกำหนดอะแดปเตอร์ คุณจะลบล้างเมธอดหลัก 3 รายการ ดังนี้

  • onCreateViewHolder(): RecyclerView จะเรียกใช้เมธอดนี้ทุกครั้งที่ ต้องสร้าง ViewHolder ใหม่ เมธอดนี้จะสร้างและเริ่มต้น ViewHolder และ View ที่เกี่ยวข้อง แต่ไม่ได้กรอกเนื้อหาของมุมมอง เนื่องจากยังไม่ได้เชื่อมโยง ViewHolder กับข้อมูลที่เฉพาะเจาะจง

  • onBindViewHolder(): RecyclerView เรียกใช้เมธอดนี้เพื่อเชื่อมโยง ViewHolder กับข้อมูล เมธอดจะดึงข้อมูลที่เหมาะสมและใช้ข้อมูลนั้นเพื่อกรอกเลย์เอาต์ของ View Holder ตัวอย่างเช่น หาก RecyclerView แสดงรายการชื่อ วิธีการอาจค้นหาชื่อที่เหมาะสมในรายการและกรอกลงในวิดเจ็ต TextView ของผู้ถือมุมมอง

  • getItemCount(): RecyclerView เรียกใช้เมธอดนี้เพื่อรับขนาดของชุดข้อมูล ตัวอย่างเช่น ในแอปสมุดที่อยู่ ค่านี้อาจเป็นจำนวนที่อยู่ทั้งหมด RecyclerView ใช้สิ่งนี้เพื่อพิจารณาว่าเมื่อใดที่ไม่มีรายการที่แสดงได้อีก

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

Kotlin

class CustomAdapter(private val dataSet: Array<String>) :
        RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

    /**
     * Provide a reference to the type of views that you are using
     * (custom ViewHolder)
     */
    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView

        init {
            // Define click listener for the ViewHolder's View
            textView = view.findViewById(R.id.textView)
        }
    }

    // Create new views (invoked by the layout manager)
    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
        // Create a new view, which defines the UI of the list item
        val view = LayoutInflater.from(viewGroup.context)
                .inflate(R.layout.text_row_item, viewGroup, false)

        return ViewHolder(view)
    }

    // Replace the contents of a view (invoked by the layout manager)
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {

        // Get element from your dataset at this position and replace the
        // contents of the view with that element
        viewHolder.textView.text = dataSet[position]
    }

    // Return the size of your dataset (invoked by the layout manager)
    override fun getItemCount() = dataSet.size

}

Java

public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {

    private String[] localDataSet;

    /**
     * Provide a reference to the type of views that you are using
     * (custom ViewHolder)
     */
    public static class ViewHolder extends RecyclerView.ViewHolder {
        private final TextView textView;

        public ViewHolder(View view) {
            super(view);
            // Define click listener for the ViewHolder's View

            textView = (TextView) view.findViewById(R.id.textView);
        }

        public TextView getTextView() {
            return textView;
        }
    }

    /**
     * Initialize the dataset of the Adapter
     *
     * @param dataSet String[] containing the data to populate views to be used
     * by RecyclerView
     */
    public CustomAdapter(String[] dataSet) {
        localDataSet = dataSet;
    }

    // Create new views (invoked by the layout manager)
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        // Create a new view, which defines the UI of the list item
        View view = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.text_row_item, viewGroup, false);

        return new ViewHolder(view);
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, final int position) {

        // Get element from your dataset at this position and replace the
        // contents of the view with that element
        viewHolder.getTextView().setText(localDataSet[position]);
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return localDataSet.length;
    }
}

เลย์เอาต์ของรายการมุมมองแต่ละรายการจะกำหนดไว้ในไฟล์เลย์เอาต์ XML ตามปกติ ในกรณีนี้ แอปจะมีไฟล์ text_row_item.xml ดังนี้

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/list_item_height"
    android:layout_marginLeft="@dimen/margin_medium"
    android:layout_marginRight="@dimen/margin_medium"
    android:gravity="center_vertical">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/element_text"/>
</FrameLayout>

ขั้นตอนถัดไป

ข้อมูลโค้ดต่อไปนี้แสดงวิธีใช้ RecyclerView

Kotlin

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val dataset = arrayOf("January", "February", "March")
        val customAdapter = CustomAdapter(dataset)

        val recyclerView: RecyclerView = findViewById(R.id.recycler_view)
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = customAdapter

    }

}

Java

RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.layoutManager = new LinearLayoutManager(this)
recyclerView.setAdapter(customAdapter);

นอกจากนี้ ไลบรารียังมีวิธีต่างๆ มากมายในการปรับแต่งการติดตั้งใช้งาน ดูข้อมูลเพิ่มเติมได้ที่การปรับแต่ง RecyclerView ขั้นสูง

เปิดใช้การแสดงผลแบบไร้ขอบ

ทำตามขั้นตอนต่อไปนี้เพื่อเปิดใช้การแสดงผลแบบไร้ขอบสำหรับ RecyclerView

  • ตั้งค่าการแสดงผลแบบไร้ขอบที่เข้ากันได้แบบย้อนหลังโดยเรียกใช้ enableEdgeToEdge()
  • หากรายการในลิสต์ทับซ้อนกับแถบระบบในตอนแรก ให้ใช้ Insets กับ RecyclerView ซึ่งทำได้โดยตั้งค่า android:fitsSystemWindows เป็น true หรือใช้ ViewCompat.setOnApplyWindowInsetsListener
  • อนุญาตให้รายการวาดใต้แถบระบบขณะเลื่อนโดยตั้งค่า android:clipToPadding เป็น false ใน RecyclerView

วิดีโอต่อไปนี้แสดง RecyclerView ที่ปิดใช้ (ซ้าย) และเปิดใช้ (ขวา) จอแสดงผลแบบขอบจรดขอบ

ตัวอย่างโค้ดแทรก

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(
  findViewById(R.id.my_recycler_view)
  ) { v, insets ->
      val innerPadding = insets.getInsets(
          WindowInsetsCompat.Type.systemBars()
                  or WindowInsetsCompat.Type.displayCutout()
          // If using EditText, also add
          // "or WindowInsetsCompat.Type.ime()" to
          // maintain focus when opening the IME
      )
      v.setPadding(
          innerPadding.left,
          innerPadding.top,
          innerPadding.right,
          innerPadding.bottom)
      insets
  }
  

Java

ViewCompat.setOnApplyWindowInsetsListener(
  activity.findViewById(R.id.my_recycler_view),
  (v, insets) -> {
      Insets innerPadding = insets.getInsets(
              WindowInsetsCompat.Type.systemBars() |
                      WindowInsetsCompat.Type.displayCutout()
              // If using EditText, also add
              // "| WindowInsetsCompat.Type.ime()" to
              // maintain focus when opening the IME
      );
      v.setPadding(
              innerPadding.left,
              innerPadding.top,
              innerPadding.right,
              innerPadding.bottom
      );
      return insets;
  }
);
  

RecyclerView XML:

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/my_recycler_view"
    android:clipToPadding="false"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบบน Android ได้จากแหล่งข้อมูลต่อไปนี้

แอปตัวอย่าง