3. Создание списка с помощью виджета RecyclerView

В Android, начиная с версии 5.0, доступны новые View-элементы экрана, которые упрощают работу со списками: RecyclerView и CardView. С помощью этих компонентов приложение будет выглядеть и вести себя в соответствии с рекомендациями Google Material Design.

Виджет RecylerView является более совершенной и гибкой версией ListView.

В модели RecyclerView вывод данных обеспечивают несколько разных компонентов. Общим контейнером для вывода информации является объект RecyclerView, который вам необходимо добавить в макет. RecyclerView заполняет себя представлениями, которые предоставляются layout manager, который вы предоставляете. Вы можете использовать один из стандартных layout manager, или реализовать собственный.

Представления в списке представлены объектами view holder. Эти объекты являются экземплярами класса, который вы определяете с помощью наследования класса RecyclerView.ViewHolder. Каждый view holder ответственный за вывод одного пункта списка. К приему, если ваш список выводит музыкальную коллекцию, то каждый view holder отвечает за вывод одного альбома. RecyclerView создает столько объектов view holder, сколько помещается в видимую часть списка, плюс несколько дополнительных объектов. Когда пользователь осуществляет прокрутку списка, RecyclerView повторно использует объекты view holder, которые вышли за пределы видимой части списка.

Объекты view holder управляются адаптером, который создается разработчиком с помощью наследования от класса RecyclerView.Adapter. Адаптер создает требуемое количество объектов view holder. Адаптер также осуществляет привязку объектов view holder к данным с помощью метода onBindViewHolder(). В метод передается позиция объекта view holder в списке, чтобы определить - какими данными необходимо заполнить view holder.

Для подключения элементов CardView и RecyclerView, мы должны добавить соответствующие библиотеки

implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0'

в раздел dependencies файла build.gradle.

Android Support Library - набор библиотек, который позволяет использовать нововведения платформы Android в более старых версиях операционной системы.

Подробнее - https://developer.android.com/topic/libraries/support-library/

Элемент CardView

CardView - контейнер для размещения информации в виде однотипных карточек. Элемент CardView будет служить пунктом списка для нашего примера.

Более подробно про CardView читайте здесь - https://developer.android.com/guide/topics/ui/layout/cardview

Добавим новый layout-ресурс, который будет представлять собой пункт списка.

Добавление RecyclerView в макет Activity

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

Настройка RecyclerView в классе Activity

Получим ссылку на объект RecyclerView. Если вы уверены, что размер RecyclerView не будет изменяться, вы можете вызвать метод setHasFixedSize(true).

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

  • LinearLayoutManager

  • GridLayoutManager

  • StaggeredGridLayoutManager

В нашем примере мы будем использовать LinearLayoutManager. По умолчанию он обеспечивает вид RecyclerView аналогично ListView.

MainActivity.java
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main_second);
    
        RecyclerView rv = findViewById(R.id.list);
        rv.setHasFixedSize(true);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        rv.setLayoutManager(layoutManager);
    }

Аналогично ListView, в RecyclerView нужен адаптер для доступа к его данным. Чтобы создать адаптер для RecyclerView, наследуемся от RecyclerView.Adapter. Этот адаптер представляет шаблон проектирования viewholder, подразумевающий использование пользовательского класса, который расширяет RecyclerView.ViewHolder. Этот паттерн сводит к минимуму количество обращений к дорогостоящему в плане ресурсов методу findViewById.

Ранее мы уже определили XML-файл макета для CardView, представляющего пункт списка. Мы собираемся использовать этот макет сейчас. Внутри конструктора собственного ViewHolder, инициализируем View, входящие в RecyclerView.

class MyAdapter extends RecyclerView.Adapter<MyAdapter.PersonViewHolder> {

    public static class PersonViewHolder extends RecyclerView.ViewHolder {

        public ImageView image;
        public TextView text;

        public PersonViewHolder(@NonNull View itemView) {
            super(itemView);

            image = itemView.findViewById(R.id.imageView);
            text = itemView.findViewById(R.id.textView);
        }
    }
}

Далее, используем конструктор адаптера RecyclerView. Так как наши данные в виде списка объектов Person, используем следующий код:

class MyAdapter extends RecyclerView.Adapter<MyAdapter.PersonViewHolder> {

    private List<Person> list;

    public MyAdapter(List<Person> list, Context context) {
        this.list = list;
    }
    
    ...
}

RecyclerView.Adapter имеет три абстрактных метода, которые мы должны переопределить. Давайте начнем с метода getItemCount. Он вернет количество элементов, присутствующих в данных. Так как наши данные в виде списка, мы просто вызываем метод size у объекта списка:

@Override
public int getItemCount() {
    return list.size();
}

Далее, следует переопределить метод onCreateViewHolder. Как следует из названия, этот метод вызывается, когда наш ViewHolder должен быть инициализирован. Мы указываем макет для каждого элемента RecyclerView. Затем LayoutInflater заполняет макет, и передает его в конструктор ViewHolder.

@NonNull
@Override
public PersonViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {

    View v = LayoutInflater.from(viewGroup.getContext())
            .inflate(R.layout.card_item, viewGroup, false);
    v.getBackground().setAlpha(0);
    PersonViewHolder holder = new PersonViewHolder(v);

    return holder;
}

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

@Override
public void onBindViewHolder(@NonNull PersonViewHolder personViewHolder, int i) {
    personViewHolder.image.setImageResource(list.get(i).getAvatarId());
    personViewHolder.text.setText(list.get(i).getName());
}

Теперь, когда адаптер готов, добавляем код для создания и использования адаптера, вызывая конструктор адаптера и отдавая его методу setAdapter нашего RecyclerView:

MainActivity.java
rv.setAdapter(new MyAdapter(people, this));

Запустим приложение и посмотрим на результат.

Статья про использование RecyclerView

В конце приведем листинги созданных классов

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main_second);

        List<Person> people = new ArrayList<>();
        people.add(new Person("Вася", R.drawable.face1));
        people.add(new Person("Петя", R.drawable.face2));
        people.add(new Person("Сережа", R.drawable.face3));
        people.add(new Person("Игорь", R.drawable.face4));
        people.add(new Person("Коля", R.drawable.face5));

        RecyclerView rv = findViewById(R.id.list);
        rv.setHasFixedSize(true);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        rv.setLayoutManager(layoutManager);

        rv.setAdapter(new MyAdapter(people, this));
    }
}

class MyAdapter extends RecyclerView.Adapter<MyAdapter.PersonViewHolder> {

    private List<Person> list;

    public MyAdapter(List<Person> list, Context context) {
        this.list = list;
    }

    @NonNull
    @Override
    public PersonViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {

        View v = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.card_item, viewGroup, false);
        v.getBackground().setAlpha(0);
        PersonViewHolder holder = new PersonViewHolder(v);

        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull PersonViewHolder personViewHolder, int i) {
        personViewHolder.image.setImageResource(list.get(i).getAvatarId());
        personViewHolder.text.setText(list.get(i).getName());
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    public static class PersonViewHolder extends RecyclerView.ViewHolder {

        public ImageView image;
        public TextView text;

        public PersonViewHolder(@NonNull View itemView) {
            super(itemView);

            image = itemView.findViewById(R.id.imageView);
            text = itemView.findViewById(R.id.textView);
        }
    }
}

class Person {

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAvatarId() {
        return avatarId;
    }

    public void setAvatarId(int avatarId) {
        this.avatarId = avatarId;
    }

    private String name;
    private int avatarId;

    public Person(String name, int avatarId) {
        this.name = name;
        this.avatarId = avatarId;
    }
}

Last updated