إنشاء قوائم ديناميكية باستخدام RecyclerView   أحد مكونات Android Jetpack.

تجربة طريقة "الكتابة"
‫Jetpack Compose هي مجموعة أدوات واجهة المستخدم التي يُنصح باستخدامها على Android. تعرَّف على كيفية استخدام التنسيقات في ميزة "الكتابة الذكية".

تسهّل أداة RecyclerView عرض مجموعات كبيرة من البيانات بكفاءة. يمكنك توفير البيانات وتحديد شكل كل عنصر، وستنشئ مكتبة RecyclerView العناصر ديناميكيًا عند الحاجة إليها.

وكما يوحي الاسم، فإنّ RecyclerView يعيد استخدام هذه العناصر الفردية. عندما يتم تمرير عنصر خارج الشاشة، لا يزيل RecyclerView طريقة العرض الخاصة به. بدلاً من ذلك، يعيد RecyclerView استخدام طريقة العرض للعناصر الجديدة التي تم الانتقال إليها على الشاشة. تعمل أداة RecyclerView على تحسين الأداء وسرعة استجابة تطبيقك، كما أنّها تقلّل من استهلاك الطاقة.

الفئات الرئيسية

تعمل عدة فئات معًا لإنشاء قائمتك الديناميكية.

  • RecyclerView هو ViewGroup الذي يحتوي على المشاهدات المطابقة لبياناتك. وهي طريقة عرض بحد ذاتها، لذا يمكنك إضافة RecyclerView إلى التصميم بالطريقة نفسها التي تضيف بها أي عنصر آخر من عناصر واجهة المستخدم.

  • يتم تحديد كل عنصر فردي في القائمة من خلال عنصر حاوية العرض. عند إنشاء عنصر نائب للعرض، لا تكون هناك أي بيانات مرتبطة به. بعد إنشاء عنصر نائب للعرض، RecyclerView يربطه ببياناته. يمكنك تحديد عنصر نائب العرض من خلال توسيع RecyclerView.ViewHolder.

  • يرسل RecyclerView طلبات للحصول على طرق العرض، ويربط طرق العرض بالبيانات من خلال استدعاء طرق في المحوّل. يمكنك تحديد المحوّل من خلال توسيع RecyclerView.Adapter.

  • يعمل مدير التنسيق على ترتيب العناصر الفردية في قائمتك. يمكنك استخدام أحد أدوات إدارة التنسيق التي توفّرها مكتبة RecyclerView، أو يمكنك تحديد أداة خاصة بك. تستند جميع أدوات إدارة التنسيق إلى فئة LayoutManager المجردة في المكتبة.

يمكنك الاطّلاع على كيفية عمل كل الأجزاء معًا في تطبيق RecyclerView النموذجي (Kotlin) أو تطبيق RecyclerView النموذجي (Java).

خطوات تنفيذ RecyclerView

إذا كنت ستستخدم RecyclerView، هناك بعض الإجراءات التي عليك اتّخاذها، وسيتم شرحها بالتفصيل في الأقسام التالية.

  1. حدِّد شكل القائمة أو الشبكة. في العادة، يمكنك استخدام أحد أدوات إدارة التنسيق العادية في مكتبة RecyclerView.

  2. صمِّم شكل كل عنصر في القائمة وطريقة عمله. استنادًا إلى هذا التصميم، وسِّع نطاق الفئة ViewHolder. يوفّر إصدار ViewHolder جميع الوظائف لعناصر القائمة. حاوية العرض هي غلاف حول View، وتتم إدارة هذا العرض من خلال RecyclerView.

  3. حدِّد Adapter الذي يربط بياناتك بطرق العرض ViewHolder.

تتوفّر أيضًا خيارات تخصيص متقدّمة تتيح لك تخصيص RecyclerView حسب احتياجاتك بالضبط.

التخطيط للتصميم

يتم ترتيب العناصر في RecyclerView حسب فئة LayoutManager. توفّر مكتبة RecyclerView ثلاثة أدوات لتنظيم التصميم، وتتعامل هذه الأدوات مع حالات التصميم الأكثر شيوعًا:

  • LinearLayoutManager ترتّب العناصر في قائمة أحادية البُعد.
  • GridLayoutManager ترتّب العناصر في شبكة ثنائية الأبعاد:
    • إذا تم ترتيب الشبكة عموديًا، تحاول GridLayoutManager أن يكون لجميع العناصر في كل صف العرض والارتفاع نفسهما، ولكن يمكن أن يكون للصفوف المختلفة ارتفاعات مختلفة.
    • إذا تم ترتيب الشبكة أفقيًا، تحاول GridLayoutManager أن يكون لجميع العناصر في كل عمود العرض والارتفاع نفسهما، ولكن يمكن أن يكون للأعمدة المختلفة عروض مختلفة.
  • StaggeredGridLayoutManager يشبه GridLayoutManager، ولكنّه لا يشترط أن تتساوى ارتفاعات العناصر في الصف الواحد (بالنسبة إلى الشبكات العمودية) أو أن تتساوى عروض العناصر في العمود الواحد (بالنسبة إلى الشبكات الأفقية). ونتيجةً لذلك، قد يتم إزاحة العناصر في صف أو عمود عن بعضها البعض.

عليك أيضًا تصميم تخطيط العناصر الفردية. تحتاج إلى هذا التصميم عند تصميم حاوية العرض، كما هو موضّح في القسم التالي.

تنفيذ المحوّل وعنصر العرض

بعد تحديد التنسيق، عليك تنفيذ Adapter وViewHolder. تعمل هاتان الفئتان معًا لتحديد طريقة عرض بياناتك. ViewHolder هو برنامج تضمين حول View يحتوي على تنسيق عنصر فردي في القائمة. ينشئ Adapter عناصر ViewHolder حسب الحاجة، ويضبط أيضًا البيانات الخاصة بطرق العرض هذه. تُعرف عملية ربط طرق العرض ببياناتها باسم الربط.

عند تحديد المحوّل، يمكنك إلغاء ثلاث طرق رئيسية:

  • onCreateViewHolder(): يستدعي RecyclerView هذه الطريقة عندما يحتاج إلى إنشاء ViewHolder جديد. تنشئ الطريقة ViewHolder وView المرتبط بها وتهيّئهما، ولكنها لا تملأ محتويات العرض، لأنّ ViewHolder لم يتم ربطه بعد ببيانات معيّنة.

  • onBindViewHolder(): تستدعي RecyclerView هذه الطريقة لربط ViewHolder بالبيانات. يجلب هذا الإجراء البيانات المناسبة ويستخدمها لملء تصميم حاوية العرض. على سبيل المثال، إذا كان 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().
  • إذا كانت عناصر القائمة تتداخل في البداية مع أشرطة النظام، طبِّق عمليات إدراج على 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، يُرجى الرجوع إلى المراجع التالية.

نماذج التطبيقات