RecyclerView и CardView. Новые виджеты в Android L

Как известно, в июне проходила очередная конференция Google I/O, в ходе которой было вкратце рассказано о двух новых виджетах. И поскольку на хабре об этих виджетах пока ничего не написано, я решил написать эту статью с простенькой демонстрацией.

Демо приложение доступно на гитхабе по ссылке.

Собственно, под катом можно прочесть пару слов о RecyclerView, CardView и о подключении соответствующих библиотек в Gradle. Также, можно посмотреть коротенькое видео, демонстрирующее стандартную анимацию операций со списком в RecyclerView.

RecyclerView


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

1. Обязательное использование паттерна ViewHolder. Если при использовании ListView можно было из-за отсутствия опыта использовать адаптер, создающий с нуля отдельное view для каждого элемента списка, что при большом размере списка могло обернуться меньшей отзывчивостью UI и использованием лишней памяти, то при работе с RecyclerView разработчика насильно приводят к имплементации этого паттерна. Посмотрим, как это выглядит в коде адаптера для RecyclerView:

/**
 * Класс адаптера наследуется от RecyclerView.Adapter с указанием класса, который будет хранить ссылки на виджеты элемента списка, т.е. класса, имплементирующего ViewHolder. В нашем случае класс объявлен внутри класса адаптера.
 */
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {

    private List<Record> records;

    public RecyclerViewAdapter(List<Record> records) {
        this.records = records;
    }

    /**
     * Создание новых View и ViewHolder элемента списка, которые впоследствии могут переиспользоваться.
     */
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recyclerview_item, viewGroup, false);
        return new ViewHolder(v);
    }

    /**
     * Заполнение виджетов View данными из элемента списка с номером i
     */
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        Record record = records.get(i);
        viewHolder.name.setText(record.getName());
    }

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

    /**
     * Реализация класса ViewHolder, хранящего ссылки на виджеты.
     */
    class ViewHolder extends RecyclerView.ViewHolder {
        private TextView name;
        private ImageView icon;

        public ViewHolder(View itemView) {
            super(itemView);
            name = (TextView) itemView.findViewById(R.id.recyclerViewItemName);
            icon = (ImageView) itemView.findViewById(R.id.recyclerViewItemIcon);
        }
    }
}


2. LayoutManager. Для использования RecyclerView кроме адаптера вам необходимо передать ему с помощью метода setLayoutManager() объект класса, реализующего LayoutManager. Этот класс отвечает за работу с адаптером, именно он решает, переиспользовать View или создать новый, и соответственно, именно он дёргает методы onCreateViewHolder(), onBindViewHolder() и getItemCount() адаптера. Пока доступна только одна реализация этого класса — LinearLayoutManager, для создания кастомного LayoutManager, необходимо унаследоваться от RecyclerView.LayoutManager.

3. Анимация операций со списком. Если вы смотрели презентацию дизайна Material, то могли заметить, что одной из основных его особенностей является плавность UI, которая достигается с помощью повсеместного использования анимации. Наверняка, при особом желании можно добавить анимацию и в ListView, мне пока не приходилось этим заниматься, но в RecyclerView это делается парой строчек кода:
— для объекта RecyclerView указывается класс, имплементирующий анимацию:

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

— при добавлении или удалении элемента списка вызывается метод адаптера notifyItemInserted(int position) или notifyItemRemoved(int position) соответственно.
При желании можно написать собственную реализацию анимации, унаследовавшись от RecyclerView.ItemAnimator.

4. Сырость. Нужно помнить, что виджет пока сыроват, и, возможно, его ждут существенные изменения. Например сейчас в RecyclerView нет возможности задать header и footer списка, да и вообще в интернете о нём пока довольно мало информации.

Собственно и всё о RecyclerView, на гитхабе я выложил простенькое приложение, демонстрирующее, как можно использовать этот виджет. Запустив его, можно полюбоваться на анимацию добавления/удаления элемента (а можно посмотреть видео).





CardView


CardView — это виджет, имплементирующий такой элемент дизайна Material, как карточка. По сути это контейнер, у которого можно задавать радиус скругленности углов, цвет карточки и высоту по оси z.
Например, вот такой код верстки:
 <android.support.v7.widget.CardView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:id="@+id/cardView"
        card_view:cardCornerRadius="6dp"
        card_view:cardBackgroundColor="#84ffff">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">


            <TextView
                android:id="@+id/info_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Test CardView"
                android:layout_centerInParent="true"
                android:textSize="18sp"
                android:textColor="#fff"/>
        </RelativeLayout>
</android.support.v7.widget.CardView>

даст нам следующий результат:



Сборка проекта, использующего новые виджеты

Для сборки с помощью Gradle нужно добавить в dependencies зависимости:
compile 'com.android.support:cardview-v7:+’
compile 'com.android.support:recyclerview-v7:+’

Но эти библиотеки автоматически меняют minSdk нашей сборки на версию L, поэтому для нормальной работы в AndroidManifest следует прописать параметр:

<uses-sdk tools:node="replace" />
Поделиться публикацией
Комментарии 15
    –1
    Спасибо, очень познавательно. Я совсем недавно узнал про ViewHolder, думал переделывать все адаптеры на него, а тут уже и готовую реализацию представили.
    Не совсем понял про LinearLayoutManager. Из названия следует, что он работает только с LinearLayout? Хотя казалось бы, причем тут вообще разница в лейаутах. Или сам ListView должен быть внутри Linearlayout?
      0
      Не уверен, но кажется, что LinearLayoutManager так назвали, потому что он предоставляет возможность отображать только линейный вертикальный или горизонтальный список элементов. Видимо, подразумевается, что с другими LayoutManager'ами список сможет иметь какую-нибудь более причудливую форму.
      То есть слова в названии нужно группировать скорее так Linear(LayoutManager), чем так (LinearLayout)Manager.
        –1
        Это было бы очень глупо, ибо LinearLayout вполне себе отдельный элемент и людей это смутит. К тому же ListView только вертикально элементы может отображать.
      +1
      Круто, до этого мне для проекта приходилось писать свою версию ListView, чтобы сделать все те анимации, которые были нужны (в частности для многоуровневого раскрывающегося списка — стандартный не поддерживает необходимую глубину, на видео уровня всего два, потому что просто не нашел сейчас, где было больше) — а добавлять анимацию в стандартном ListView — боль… Давно этот виджет надо было обновить, молодцы.

        +2
        А теперь попробуйте реализовать кастомный LayoutManager для RecyclerView (то, собственно, ради чего все это заваривалось).
        Для примера — wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/ и wiresareobsolete.com/2014/09/recyclerview-layoutmanager-2/
        Так, чтобы оценить масштаб задачи и объем ручной работы.
          0
          Не занимаюсь разработкой под android, но по мне в приведенных в ссылках примерах кода довольно мало для нестандартного use case.
            +1
            По ссылкам, как раз чтобы не приводить всю простыню кода, оставлены только важные для понимания куски. Весь код лежит в этом репозитории: github.com/devunwired/recyclerview-playground
          +1
          Но эти библиотеки автоматически меняют minSdk нашей сборки на версию L

          Почему? Ведь в их названии есть v7. К тому же, вот что написано здесь
          The RecyclerView and CardView widgets are included in the Android L Developer Preview Support Library, so they are available in earlier versions of Android with these limitations:

          CardView falls back to a programmatic shadow implementation using additional padding.
          CardView does not clip its children views that intersect with rounded corners.
            0
            Да, мне тоже это кажется странным, но если не прописать <uses-sdk tools:node=«replace» />, то Gradle, насколько я понимаю, при мержинге манифестов выставит максимальный minSdk, а он у этих библиотек почему-то соответствует версии L.
            Вот здесь это подробнее описано http://www.reddit.com/r/androiddev/comments/297xli/howto_use_the_v21_support_libs_on_older_versions/
              +1
              People need to realize the reason it's platform restricted right now is because it's all still very much a WIP and will change between now and release. This is restricted to unreleased software because it's not intended to be used in production apps until it's ready.

              Видимо, разработчики Android таким образом решили намекнуть, что использовать эти библиотеки до релиза можно только на свой страх и риск.
          0
          Понятно, спасибо за объяснение.
            0
            Как в RecyclerView понять, что скролл достиг конца, чтобы загрузить еще данных? В ListView я делал так:

            public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                boolean loadMore = firstVisibleItem + visibleItemCount >= totalItemCount;
                if (loadMore && !getLoaderManager().hasRunningLoaders()) {
                    getLoaderManager().restartLoader(LOADER_ID);
                }
            }
            
              0
              stackoverflow.com/questions/26543131/how-to-implement-endless-list-with-recyclerview
              0
              Если при использовании ListView можно было из-за отсутствия опыта использовать адаптер, создающий с нуля отдельное view для каждого элемента списка, что при большом размере списка могло обернуться меньшей отзывчивостью UI и использованием лишней памяти

              Э… а при чем тут паттерн ViewHolder? Его единственная цель — не вызывать findViewById() слишком часто. Память и создание отдельного элемента списка для каждой вью тут ни при чем…

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое