สร้างลิสต์แบบไดนามิกด้วย RecyclerView   ซึ่งเป็นส่วนหนึ่งของ Android Jetpack.

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

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

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

คลาสหลัก

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ติดตั้งใช้งาน Adapter และ View Holder

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

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

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

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

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

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

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()
  • หากรายการในลิสต์ซ้อนทับแถบระบบในตอนแรก ให้ใช้ระยะขอบภายในกับ 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;
  }
);
  

XML ของ RecyclerView

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

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

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

ตัวอย่างแอป