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

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

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

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

Основные классы

Для создания динамического списка используется несколько классов.

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

  • Каждый отдельный элемент в списке определяется объектом view holder. При создании держателя представления с ним не связаны никакие данные. После создания держателя представления RecyclerView связывает его с данными. Вы определяете держатель представления, расширяя RecyclerView.ViewHolder.

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

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

Чтобы увидеть, как все эти элементы работают вместе, ознакомьтесь с примерами приложений RecyclerView на Kotlin и Java.

Как реализовать RecyclerView

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

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

  2. Настройте внешний вид и поведение каждого элемента в списке. На основе этого проекта расширьте класс ViewHolder. Ваша версия 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;
  }
);
  

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

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