Статьи

Освоение сложных списков с помощью Android RecyclerView

Эта статья была обновлена ​​1 февраля 2017 г. для Android 7.1 (уровень API 25)

Чтобы узнать больше об Android, просмотрите наш учебник « Вывод данных в пользовательский интерфейс приложения Android» .

RecyclerView был представлен с дизайном материалов Google в Android 5.0 Lollipop.

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

Идея утилизации представления была в Android с версии 1 в форме ListView . Идея проста: представить большой набор данных с использованием небольшого набора представлений, переработав и связав эти представления.

RecyclerView — это более гибкий шаблон RecyclerView использования представлений, чем ListView и GridView . Что отличает RecyclerView от его предшественников, так это то, что он фокусируется только на представлениях RecyclerView использования. Все остальные действия, необходимые для создания представления, такие как представление набора данных или раздувание представлений, делегируются подключаемым классам, и это делает его таким гибким. Сложная часть заключается в настройке этих классов для создания полнофункционального RecyclerView и об этом я расскажу в этой статье.

Чтобы использовать RecyclerView вам необходимо выполнить следующие шаги:

  1. Добавить библиотеку поддержки
  2. Добавьте RecyclerView в XML-файл макета
  3. Создать пользовательский макет строки
  4. Создайте RecyclerView.Adapter для заполнения данных в RecyclerView
  5. Создайте ViewHolder чтобы предоставить ссылку на представления для каждого элемента данных
  6. Свяжите Adapter с RecyclerView в Activity

Давайте RecyclerView с RecyclerView , вы можете найти код для окончательного проекта на GitHub .

Добавить зависимости

Откройте build.gradle (приложение) и добавьте необходимые зависимости.

 dependencies { ... compile 'com.android.support:cardview-v7:25.1.0' compile 'com.android.support:recyclerview-v7:25.1.0' } 

Синхронизируйте Gradle и все готово.

Добавьте RecyclerView в файл макета

 < RelativeLayout ... < android.support.v7.widget.RecyclerView xmlns:android = "http://schemas.android.com/apk/res/android" android:id = "@+id/recyclerview" android:layout_width = "match_parent" android:layout_height = "match_parent" /> </ RelativeLayout > 

Создать пользовательский макет строки

Строка Layout представляет макет каждого отдельного элемента, отображаемого в RecyclerView.

Создайте файл с именем row_layout.xml и добавьте в него следующее:

 < android.support.v7.widget.CardView xmlns:android = "http://schemas.android.com/apk/res/android" xmlns:app = "http://schemas.android.com/apk/res-auto" android:id = "@+id/cardView" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_marginBottom = "@dimen/activity_vertical_margin" android:clickable = "true" android:focusable = "true" android:foreground = "?android:attr/selectableItemBackground" app:cardCornerRadius = "@dimen/activity_vertical_margin" app:cardElevation = "@dimen/activity_vertical_margin" > < RelativeLayout android:layout_width = "match_parent" android:layout_height = "wrap_content" android:padding = "16dp" > < ImageView android:id = "@+id/imageView" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_alignParentLeft = "true" android:layout_alignParentTop = "true" android:layout_marginRight = "16dp" /> < TextView android:id = "@+id/title" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_alignParentTop = "true" android:layout_toRightOf = "@+id/imageView" android:text = "Title" android:textSize = "30sp" /> < TextView android:id = "@+id/description" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_below = "@+id/title" android:layout_toRightOf = "@+id/imageView" android:text = "Description" /> </ RelativeLayout > </ android.support.v7.widget.CardView > 

Просто обратите внимание: при создании макета элемента RecyclerView не забудьте добавить следующие строки в контейнер ViewGroup макета. Эти строки кода добавят волновой эффект к элементам RecyclerView.

  android:clickable="true" android:focusable="true" android:foreground="?android:attr/selectableItemBackground" 

RecyclerView будет список случайно выбранных фильмов и описание. Нам нужен класс, представляющий данные одного элемента RecyclerView , создайте файл с именем Data.java и добавьте следующее:

 public class Data { public String title; public String description; public int imageId; Data(String title, String description, int imageId) { this .title = title; this .description = description; this .imageId = imageId; } } 

Адаптер RecyclerView

RecyclerView.Adapter аналогичен адаптерам, используемым в ListView но с ViewHolder необходимым для повышения производительности. ListView имеет адаптеры для различных источников, такие как ArrayAdapter для массивов и CursorAdapter для результатов базы данных. RecyclerView.Adapter требует пользовательской реализации для передачи данных в адаптер.

Адаптер имеет три метода.

  • onCreateViewHolder() раздувает макет строки и инициализирует onCreateViewHolder() . После инициализации держателя представления он управляет методами findViewById() , находя представления один раз и перерабатывая их, чтобы избежать повторных вызовов.
  • onBindViewHolder() использует держатель представления, onCreateViewHolder() методе onCreateViewHolder() для заполнения текущей строки RecyclerView данными.

Создайте новый файл с именем Recycler_View_Adapter.java со следующим классом:

 public class Recycler_View_Adapter extends RecyclerView . Adapter < View_Holder > { List<Data> list = Collections.emptyList(); Context context; public Recycler_View_Adapter (List<Data> list, Context context) { this .list = list; this .context = context; } @Override public View_Holder onCreateViewHolder (ViewGroup parent, int viewType) { //Inflate the layout, initialize the View Holder View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_layout, parent, false ); View_Holder holder = new View_Holder(v); return holder; } @Override public void onBindViewHolder (View_Holder holder, int position) { //Use the provided View Holder on the onCreateViewHolder method to populate the current row on the RecyclerView holder.title.setText(list.get(position).title); holder.description.setText(list.get(position).description); holder.imageView.setImageResource(list.get(position).imageId); //animate(holder); } @Override public int getItemCount () { //returns the number of elements the RecyclerView will display return list.size(); } @Override public void onAttachedToRecyclerView (RecyclerView recyclerView) { super .onAttachedToRecyclerView(recyclerView); } // Insert a new item to the RecyclerView on a predefined position public void insert ( int position, Data data) { list.add(position, data); notifyItemInserted(position); } // Remove a RecyclerView item containing a specified Data object public void remove (Data data) { int position = list.indexOf(data); list.remove(position); notifyItemRemoved(position); } } 

Создание ViewHolder

RecyclerView использует ViewHolder для хранения ссылок на соответствующие представления для одной записи в RecyclerView . Это решение позволяет избежать всех вызовов метода findViewById() в адаптере, чтобы найти представления, которые будут заполнены данными.

Создайте файл с именем View_Holder.java со следующим классом:

 public class View_Holder extends RecyclerView . ViewHolder { CardView cv; TextView title; TextView description; ImageView imageView; View_Holder(View itemView) { super (itemView); cv = (CardView) itemView.findViewById(R.id.cardView); title = (TextView) itemView.findViewById(R.id.title); description = (TextView) itemView.findViewById(R.id.description); imageView = (ImageView) itemView.findViewById(R.id.imageView); } } 

LayoutManager

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

Эти реализации по умолчанию доступны:

  • LinearLayoutManager — отображает элементы в вертикальной или горизонтальной прокрутке списка.
  • GridLayoutManager — отображает элементы в сетке.
  • StaggeredGridLayoutManager — отображает элементы в шахматном порядке.

Вы можете создать собственный LayoutManager , расширив RecyclerView.LayoutManager или одну из приведенных выше реализаций и переопределив необходимые методы.

Теперь, когда RecyclerView завершен, следующий шаг — заполнить его некоторыми данными. В onCreate() класса MainActivity создайте экземпляр Recycler_View_Adapter и предоставьте этому адаптеру список данных и контекст. Метод getApplication() предоставит контекст приложения.

 List<Data> data = fill_with_data(); RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview); Recycler_View_Adapter adapter = new Recycler_View_Adapter(data, getApplication()); recyclerView.setAdapter(adapter); recyclerView.setLayoutManager( new LinearLayoutManager( this )); 

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

 public List<Data> fill_with_data () { List<Data> data = new ArrayList<>(); data.add( new Data( "Batman vs Superman" , "Following the destruction of Metropolis, Batman embarks on a personal vendetta against Superman " , R.drawable.ic_action_movie)); data.add( new Data( "X-Men: Apocalypse" , "X-Men: Apocalypse is an upcoming American superhero film based on the X-Men characters that appear in Marvel Comics " , R.drawable.ic_action_movie)); data.add( new Data( "Captain America: Civil War" , "A feud between Captain America and Iron Man leaves the Avengers in turmoil. " , R.drawable.ic_action_movie)); data.add( new Data( "Kung Fu Panda 3" , "After reuniting with his long-lost father, Po must train a village of pandas" , R.drawable.ic_action_movie)); data.add( new Data( "Warcraft" , "Fleeing their dying home to colonize another, fearsome orc warriors invade the peaceful realm of Azeroth. " , R.drawable.ic_action_movie)); data.add( new Data( "Alice in Wonderland" , "Alice in Wonderland: Through the Looking Glass " , R.drawable.ic_action_movie)); return data; } 

Вы можете найти файлы значков для кнопки ic_action_movie , упомянутой выше, здесь .

Это завершает все шаги по настройке RecyclerView.

Анимация повторяется с ItemAnimator

Пока что я объяснил все, что нужно для создания собственного RecyclerView и объяснил, что делает каждая часть структуры RecyclerView . Теперь давайте сделаем вещи более интересными с помощью анимации предметов.

RecyclerView.ItemAnimator — это класс, который определяет анимацию, выполняемую над элементами, и будет анимировать изменения ViewGroup такие как добавление / удаление / выбор, уведомленные адаптеру. DefaultItemAnimator — это базовая анимация, доступная по умолчанию с помощью RecyclerView .

Чтобы настроить DefaultItemAnimator добавьте аниматор элементов в RecyclerView . Этот код замедляет процесс добавления и удаления элементов из RecyclerView .

Добавьте это после нашего последнего кода в методе onCreate :

 RecyclerView.ItemAnimator itemAnimator = new DefaultItemAnimator(); itemAnimator.setAddDuration( 1000 ); itemAnimator.setRemoveDuration( 1000 ); recyclerView.setItemAnimator(itemAnimator); 

Add view

Remove view

Еще один подход к анимации элементов RecyclerView — использование Android-интерполяторов. Согласно developer.android.com , интерполятор определяет скорость изменения анимации.

Следующие примеры представляют две анимации, которые я реализовал, используя эти интерполяторы. Сохраните файлы в / anim / в папке ресурсов проекта.

anticipate_overshoot_interpolator.xml

 <?xml version="1.0" encoding="utf-8"?> < set xmlns:android = "http://schemas.android.com/apk/res/android" android:interpolator = "@android:anim/anticipate_overshoot_interpolator" > < translate android:fromYDelta = "-50%p" android:toYDelta = "0" android:duration = "2000" /> </ set > 

bounce_interpolator.xml

 <?xml version="1.0" encoding="utf-8"?> < set xmlns:android = "http://schemas.android.com/apk/res/android" android:interpolator = "@android:anim/bounce_interpolator" > < translate android:duration = "1500" android:fromYDelta = "-150%p" android:toYDelta = "0" /> </ set > 

Теперь вернемся к RecyclerView . Внутри класса RecyclerView Adapter добавьте следующую функцию.

 public void animate (RecyclerView.ViewHolder viewHolder) { final Animation animAnticipateOvershoot = AnimationUtils.loadAnimation(context, R.anim.bounce_interpolator); viewHolder.itemView.setAnimation(animAnticipateOvershoot); } 

Если вы присмотритесь к loadAnimation() , ему потребуется параметр контекста, и поэтому конструктор адаптера был изменен в первую очередь. Теперь мы можем анимировать RecyclerView . Внутри onBindViewHolder() вызовите функцию onBindViewHolder() и передайте параметр View_Holder класса View_Holder .

Запустите приложение и проверьте анимацию. Вот как bounce_interpolator анимация bounce_interpolator .

bounce animation

Далее давайте протестируем анимацию StaggeredGrid , но сначала я установлю для менеджера компоновки RecyclerView значение StaggeredGrid в StaggeredGrid Main Activity onCreate() с двумя столбцами и вертикальной прокруткой.

 recyclerView.setLayoutManager( new StaggeredGridLayoutManager( 2 ,StaggeredGridLayoutManager.VERTICAL)); 

anticipate_overshoot animation

Использовать этот метод анимации в RecyclerView очень просто. Если вам нужны новые анимации, просто измените тип интерполяции в приведенных выше примерах, установите параметры по своему усмотрению, и у вас будет новый стиль анимации.

onItemClickListener проблемы с настройкой onItemClickListener для элементов RecyclerView ?

Хотя отображение элементов в RecyclerView лучше с точки зрения производительности, чем его предшественники, ListView и GridView . Эти представления имеют возможность добавлять прослушиватели щелчков элементов, чтобы перехватить, какой элемент списка был нажат.
Вот как это выглядит на ListView :

 ListView listView = ...; listView.setOnItemClickListener( new AdapterView.OnItemClickListener() { @Override public void onItemClick (AdapterView<?> parent, View view, int position, long id) { } }); 

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

Создайте interface Java, как показано ниже.

 public interface RecyclerViewItemClickListener { public void onClick (View view, int position); public void onLongClick (View view, int position); } 

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

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

 public class CustomRVItemTouchListener implements RecyclerView . OnItemTouchListener { //GestureDetector to intercept touch events GestureDetector gestureDetector; private RecyclerViewItemClickListener clickListener; public CustomRVItemTouchListener (Context context, final RecyclerView recyclerView, final RecyclerViewItemClickListener clickListener) { this .clickListener = clickListener; gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapUp (MotionEvent e) { return true ; } @Override public void onLongPress (MotionEvent e) { //find the long pressed view View child = recyclerView.findChildViewUnder(e.getX(), e.getY()); if (child != null && clickListener != null ) { clickListener.onLongClick(child, recyclerView.getChildLayoutPosition(child)); } } }); } @Override public boolean onInterceptTouchEvent (RecyclerView recyclerView, MotionEvent e) { View child = recyclerView.findChildViewUnder(e.getX(), e.getY()); if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) { clickListener.onClick(child, recyclerView.getChildLayoutPosition(child)); } return false ; } @Override public void onTouchEvent (RecyclerView rv, MotionEvent e) { } @Override public void onRequestDisallowInterceptTouchEvent ( boolean disallowIntercept) { } } 

По сути, этот класс обнаруживает элемент RecyclerView в позиции (X, Y), где был нажат экран. Этот класс полезен для обоих типов кликов, созданных интерфейсом.

Наконец, вот как этот новый приемник щелчков реализован для RecyclerView .

 recyclerView.addOnItemTouchListener( new CustomRVItemTouchListener( this , recyclerView, new RecyclerViewItemClickListener() { @Override public void onClick (View view, int position) { } @Override public void onLongClick (View view, int position) { } })); 

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

Вывод

RecyclerView — это гибкий шаблон для повторного использования представлений, исключающий имитации ListView и GridView благодаря чему анимации элементов можно легко устанавливать и использовать повторно. Привыкание к разработке с помощью RecyclerView может сначала потребовать некоторой работы, но оно того стоит.

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

Чтобы узнать больше об Android, просмотрите наш учебник « Вывод данных в пользовательский интерфейс приложения Android» .