יצירת רשימות דינמיות באמצעות RecyclerView   חלק מ-Android Jetpack.

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

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

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

שיעורים מרכזיים

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

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

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

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

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

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

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

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

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

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

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

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

תכנון הפריסה

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

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

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

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

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

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

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

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

  • 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 עם תצוגה מקצה לקצה כשהיא מושבתת (מימין) ומופעלת (משמאל):

קוד לדוגמה של מודעת Inset:

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

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