יצירת רשימות דינמיות באמצעות RecyclerView   בארגז הכלים Android Jetpack.

איך עושים את זה ב-Compose
‫Jetpack Compose היא ערכת הכלים המומלצת לפיתוח ממשק משתמש ב-Android. איך עובדים עם פריסות ב-Compose.

‫RecyclerView מאפשר להציג ביעילות קבוצות גדולות של נתונים. אתם מספקים את הנתונים ומגדירים איך כל פריט נראה, וספריית RecyclerView יוצרת את הרכיבים באופן דינמי כשצריך אותם.

כמו שהשם מרמז, RecyclerView ממחזר את הרכיבים הנפרדים האלה. כשפריט נגלל אל מחוץ למסך, RecyclerView לא משמיד את התצוגה שלו. במקום זאת, RecyclerView משתמש מחדש בתצוגה לפריטים חדשים שנגללו למסך. ‫RecyclerView משפר את הביצועים ואת מהירות התגובה של האפליקציה, ומפחית את צריכת החשמל.

כיתות מפתח

כמה מחלקות פועלות יחד כדי ליצור את הרשימה הדינמית.

  • ‫RecyclerView הוא ה-ViewGroup שמכיל את התצוגות שמתאימות לנתונים שלכם. הוא בעצמו תצוגה, אז מוסיפים את RecyclerView לפריסה כמו שמוסיפים כל רכיב בממשק המשתמש.

  • כל רכיב בודד ברשימה מוגדר על ידי אובייקט view holder. כשיוצרים את ה-view holder, לא משויכים אליו נתונים. אחרי שיוצרים את ה-view holder, ה-RecyclerView מקשר אותו לנתונים שלו. מגדירים את מחזיק התצוגה על ידי הרחבה של RecyclerView.ViewHolder.

  • ה-RecyclerView מבקש תצוגות וקושר אותן לנתונים שלהן באמצעות קריאה לשיטות במתאם. כדי להגדיר את המתאם, מרחיבים את RecyclerView.Adapter.

  • מנהל הפריסה מסדר את הרכיבים הנפרדים ברשימה. אתם יכולים להשתמש באחד ממנהלי הפריסה שספריית RecyclerView מספקת, או להגדיר מנהל פריסה משלכם. כל מנהלי הפריסות מבוססים על המחלקה המופשטת LayoutManager של הספרייה.

אתם יכולים לראות איך כל החלקים משתלבים יחד באפליקציה לדוגמה של RecyclerView (Kotlin) או באפליקציה לדוגמה של RecyclerView (Java).

שלבים להטמעת RecyclerView

אם אתם מתכוונים להשתמש ב-RecyclerView, יש כמה דברים שאתם צריכים לעשות. הם מוסברים בפירוט בקטעים הבאים.

  1. בוחרים איך הרשימה או הרשת ייראו. בדרך כלל, אפשר להשתמש באחד ממנהלי הפריסה הרגילים של ספריית RecyclerView.

  2. מעצבים את המראה וההתנהגות של כל רכיב ברשימה. על סמך העיצוב הזה, הרחיבו את המחלקה ViewHolder. הגרסה של ViewHolder מספקת את כל הפונקציונליות של הפריטים ברשימה. ה-view holder הוא wrapper סביב View, והתצוגה הזו מנוהלת על ידי RecyclerView.

  3. מגדירים את Adapter שמשייך את הנתונים לתצוגות המפורטות של ViewHolder.

יש גם אפשרויות מתקדמות להתאמה אישית שמאפשרות להתאים את RecyclerView לצרכים הספציפיים שלכם.

תכנון הפריסה

הפריטים ב-RecyclerView מסודרים לפי מחלקה מסוג LayoutManager. ספריית RecyclerView מספקת שלושה מנהלי פריסה, שמטפלים במצבי הפריסה הנפוצים ביותר:

  • LinearLayoutManager מסדר את הפריטים ברשימה חד-ממדית.
  • GridLayoutManager מסדר את הפריטים ברשת דו-ממדית:
    • אם הרשת מסודרת אנכית, GridLayoutManager מנסה להגדיר לכל הרכיבים בכל שורה את אותו רוחב ואותו גובה, אבל לשורות שונות יכולים להיות גבהים שונים.
    • אם הרשת מסודרת אופקית, GridLayoutManager מנסה להגדיר לכל הרכיבים בכל עמודה את אותו רוחב ואותו גובה, אבל לעמודות שונות יכולים להיות רוחבים שונים.
  • StaggeredGridLayoutManager דומה ל-GridLayoutManager, אבל לא מחייב שלפריטים בשורה יהיה אותו גובה (ברשתות אנכיות) או שלפריטים באותה עמודה יהיה אותו רוחב (ברשתות אופקיות). כתוצאה מכך, הפריטים בשורה או בעמודה עלולים להיות מוסטים זה מזה.

צריך גם לעצב את הפריסה של הפריטים הנפרדים. תצטרכו את הפריסה הזו כשתעצבו את ה-ViewHolder, כפי שמתואר בקטע הבא.

הטמעת המתאם ומחזיק התצוגה

אחרי שבוחרים את הפריסה, צריך להטמיע את Adapter ו-ViewHolder. שתי המחלקות האלה פועלות יחד כדי להגדיר איך הנתונים יוצגו. ‫ViewHolder הוא מעטפת של View שמכיל את הפריסה של פריט בודד ברשימה. ה-Adapter יוצר אובייקטים מסוג ViewHolder לפי הצורך, וגם מגדיר את הנתונים לתצוגות האלה. התהליך של שיוך תצוגות לנתונים שלהן נקרא קישור.

כשמגדירים את המתאם, מבטלים שלוש שיטות עיקריות:

  • ‫onCreateViewHolder(): ‫RecyclerView קורא ל-method הזה בכל פעם שצריך ליצור ViewHolder חדש. השיטה יוצרת ומאתחלת את ה-ViewHolder ואת ה-View שמשויך אליו, אבל לא ממלאת את התוכן של התצוגה – ה-ViewHolder עדיין לא קושר לנתונים ספציפיים.

  • ‫onBindViewHolder(): RecyclerView קורא ל-method הזה כדי לשייך ViewHolder לנתונים. מאחזרת את הנתונים המתאימים ומשתמשת בהם כדי למלא את הפריסה של מחזיק התצוגה. לדוגמה, אם RecyclerView מציג רשימת שמות, יכול להיות שה-method ימצא את השם המתאים ברשימה וימלא את הווידג'ט TextView של ה-view holder.

  • ‫getItemCount(): RecyclerView קורא ל-method הזה כדי לקבל את הגודל של מערך הנתונים. לדוגמה, באפליקציה של פנקס כתובות, זה יכול להיות המספר הכולל של הכתובות. ‫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;
  }
);
  

קובץ ה-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, אפשר לעיין במקורות המידע הבאים.

אפליקציות לדוגמה