Создавайте динамические списки с помощью RecyclerView , части Android Jetpack .
RecyclerView позволяет легко и эффективно отображать большие наборы данных. Вы предоставляете данные и определяете, как выглядит каждый элемент, а библиотека RecyclerView динамически создает элементы, когда они нужны.
Как следует из названия, RecyclerView перерабатывает эти отдельные элементы. Когда элемент прокручивается за пределы экрана, RecyclerView не уничтожает его вид. Вместо этого RecyclerView повторно использует вид для новых элементов, которые прокручиваются на экране. RecyclerView повышает производительность и отзывчивость вашего приложения, а также снижает потребление энергии.
Ключевые классы
Несколько классов работают вместе, создавая ваш динамический список.
RecyclerView
— этоViewGroup
, которая содержит представления, соответствующие вашим данным. Это само представление, поэтому вы добавляетеRecyclerView
в свой макет так же, как добавляете любой другой элемент пользовательского интерфейса.Каждый отдельный элемент в списке определяется объектом держателя представления . Когда создается держатель представления, с ним не связаны никакие данные. После создания держателя представления
RecyclerView
привязывает его к своим данным. Вы определяете держатель представления, расширяяRecyclerView.ViewHolder
.RecyclerView
запрашивает представления и привязывает представления к своим данным, вызывая методы в адаптере. Вы определяете адаптер, расширяяRecyclerView.Adapter
.Менеджер макета упорядочивает отдельные элементы в вашем списке. Вы можете использовать один из менеджеров макета, предоставляемых библиотекой RecyclerView, или можете определить свой собственный. Все менеджеры макета основаны на абстрактном классе
LayoutManager
библиотеки.
Вы можете увидеть, как все части сочетаются друг с другом, в примере приложения RecyclerView (Kotlin) или примере приложения RecyclerView (Java) .
Шаги по внедрению RecyclerView
Если вы собираетесь использовать RecyclerView, вам нужно сделать несколько вещей. Они подробно описаны в следующих разделах.
Решите, как будет выглядеть список или сетка. Обычно вы можете использовать один из стандартных менеджеров макетов библиотеки RecyclerView.
Спроектируйте, как выглядит и ведет себя каждый элемент в списке. На основе этого дизайна расширьте класс
ViewHolder
. Ваша версияViewHolder
предоставляет всю функциональность для элементов вашего списка. Ваш держатель представления — это обертка вокругView
, и это представление управляетсяRecyclerView
.Определите
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);
Библиотека также предлагает множество способов настройки вашей реализации. Для получения дополнительной информации см. Advanced RecyclerView customization .
Включить отображение от края до края
Чтобы включить отображение от края до края для 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 можно найти на следующих ресурсах.