Создавайте динамические списки с помощью RecyclerView , части Android Jetpack .

Попробуйте способ «Композиции»
Jetpack Compose — рекомендуемый набор инструментов для разработки пользовательского интерфейса для Android. Узнайте, как работать с макетами в Compose.

RecyclerView позволяет легко и эффективно отображать большие наборы данных. Вы предоставляете данные и определяете внешний вид каждого элемента, а библиотека RecyclerView динамически создает элементы по мере необходимости.

Как следует из названия, RecyclerView перерабатывает отдельные элементы. Когда элемент прокручивается за пределы экрана, RecyclerView не уничтожает его вид. Вместо этого RecyclerView повторно использует вид для новых элементов, прокрученных на экране. RecyclerView повышает производительность и скорость отклика вашего приложения, а также снижает энергопотребление.

Ключевые классы

Несколько классов работают вместе, создавая ваш динамический список.

  • RecyclerView — это ViewGroup , содержащая представления, соответствующие вашим данным. RecyclerView сам по себе является представлением, поэтому вы добавляете RecyclerView в макет так же, как и любой другой элемент пользовательского интерфейса.

  • Каждый отдельный элемент в списке определяется объектом- держателем представления . При создании держателя представления с ним не связаны никакие данные. После создания держателя представления RecyclerView привязывает его к своим данным. Держатель представления определяется путём расширения RecyclerView.ViewHolder .

  • RecyclerView запрашивает представления и привязывает их к своим данным, вызывая методы в адаптере. Адаптер определяется путём расширения RecyclerView.Adapter .

  • Менеджер макетов упорядочивает отдельные элементы в списке. Вы можете использовать один из менеджеров макетов, предоставляемых библиотекой RecyclerView, или создать свой собственный. Все менеджеры макетов основаны на абстрактном классе LayoutManager этой библиотеки.

Вы можете увидеть, как все части соединяются вместе, в примере приложения RecyclerView (Kotlin) или примере приложения RecyclerView (Java) .

Шаги по внедрению RecyclerView

Если вы собираетесь использовать RecyclerView, вам нужно сделать несколько вещей. Они подробно описаны в следующих разделах.

  1. Определите, как будет выглядеть список или сетка. Обычно можно использовать один из стандартных менеджеров макетов библиотеки RecyclerView.

  2. Спроектируйте внешний вид и поведение каждого элемента списка. Основываясь на этом, расширьте класс ViewHolder . Ваша версия ViewHolder обеспечивает всю функциональность для элементов списка. Ваш View Holder — это обёртка вокруг 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 .

Котлин

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

}

Ява

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 .

Котлин

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

    }

}

Ява

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 с отключенным (слева) и включенным (справа) дисплеем от края до края:

Пример вставленного кода:

Котлин

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
  }
  

Ява

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 можно найти в следующих ресурсах.

Примеры приложений