Вы находитесь на странице: 1из 10

Отображение

контента
1
RecyclerView

— довольно продвинутый компонент для отображения списков


элементов. Может быть вертикальным, горизонтальным, таблицей.
Заменил устаревшие ListView и GridView. К преимуществам
относятся скорость работы и простота анимирования.

Для подключения RecyclerView к проекту добавляем в


app/build.gradle в раздел dependencies:

compile 'com.android.support:design:25.3.1'

// версию библиотеки заменить на актуальную.


IDE сама подскажет, если это нужно.

• В файле лейаута прописываем:

<android.support.v7.widget.RecyclerView
android:id="@+id/items"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/margin_small"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
/>

2
Новый для нас параметр здесь -- это app:layoutManager.
Он указывает, как будут располагаться элементы, в данном случае на
каждой строке один элемент. Обратите внимание на префикс app —
это так называемое XML namespace. Нам важно здесь только то, что в
корневом элементе в данном файле должна быть указана строка
xmlns:app="http://schemas.android.com/apk/res-auto" — скорее всего
IDE сама предложит её добавить. Просто нажимаете Alt+Enter.

В методе onCreate в активити или в onViewCreated во фрагменте


пишем:

items = (RecyclerView) view.findViewById(R.id.items);


items.setAdapter(adapter);

Здесь items — поле в активити или фрагменте, adapter — тоже поле:

private final ItemsAdapter adapter = new ItemsAdapter();

3
Адаптер
— класс, который заведует тем, какие данные и как отображать
в RecyclerView.

class ItemsAdapter extends RecyclerView.Adapter<ItemsAdapter.ItemViewHolder> {

private List<Item> items = new ArrayList<>();

@Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {return new
ItemViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item, null));}

@Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
final Item item = items.get(position);
holder.name.setText(item.name);
holder.price.setText(holder.itemView.getContext().getString(R.string.price, item.price));
}

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

void add(Item item) {


items.add(0, item);
notifyItemInserted(0);
}

static class ItemViewHolder extends RecyclerView.ViewHolder {

private final TextView name, price;

ItemViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
price = (TextView) itemView.findViewById(R.id.price);
}
}
}

4
Нужно наследоваться от RecyclerView.Adapter и в качестве
параметра указать свой ViewHolder. Чтобы понять, что такое
ViewHolder, посмотрим упрощённо, как работает RecyclerView.
Допустим, у нас 10 элементов в списке, а на экране помещается только
5. Для каждого элемента нужно выполнить 2 операции: создать View
(это делает метод onCreateViewHolder) и отобразить в нём наш элемент
(onBindViewHolder).

Когда пользователь прокручивает наш список, чтобы посмотреть


следующие элементы, мы уже не создаём для них View, а просто
отображаем новые элементы вместо тех, которые уехали с экрана в уже
созданных вьюхах. Таким образом, onCreateViewHolder вызывается
всего 5 раз, а onBindViewHolder — сколько угодно раз, если
пользователь будет прокручивать список туда-сюда.
Без механизма ViewHolder это делалось бы примерно так:

((TextView)itemView.findViewById(R.id.name)).setText(...),

т.е. мы каждый раз в onBindViewHolder вызывали бы findViewById.


Но эта операция выполняется довольно долго, т. к. надо обежать всё
дерево вьюх. Чтобы её не делать каждый раз в onBindViewHolder, мы
делаем это один раз в onCreateViewHolder и запоминаем результат в
специальном маленьком объектике — ViewHolder.

5
Рассмотрим, что у нас ещё есть в адаптере. Поле items хранит сами
наши данные. В onCreateViewHolder мы загружаем нашу вьюху
из xml (это умеет делать класс LayoutInflater) и создаём на её основе
ViewHolder, а в onBindViewHolder мы отображаем во вьюхе нужный
элемент списка.

Вся эта довольно сложная структура с адаптерами, ViewHolder и


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

Этот экран выполнен с помощью RecyclerView, причём на первой


строке одно, а на следующих совсем другое.

6
У RecyclerView есть и проблемы

Посмотрим на наш item.xml.

<FrameLayout xmlns:android="http://schemas.android.com/
apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/margin_small">

<RelativeLayout
android:id="@+id/item_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_item">

<TextView
android:id="@+id/name"
style="@style/Text.Item"
android:layout_alignParentStart="true"
android:layout_toStartOf="@+id/price" />

<TextView
android:id="@+id/price"
style="@style/Text.Item"
android:layout_alignParentEnd="true"
android:background="@color/colorShadow"
android:textStyle="bold" />
</RelativeLayout>
</FrameLayout>

7
Первое, что бросается в глаза, - зачем нужен здесь FrameLayout,
у которого только один ребёнок? Добро пожаловать в суровый мир
Андроида, здесь полно багов и странностей, и эта - одна из них.
В дизайне между элементами списка трат нарисован промежуток. И
нет нормального простого способа его реализовать в RecyclerView.
Если вы погуглите, то увидите несколько вариантов, один другого хуже,
например, обернуть весь контент в дополнительный FrameLayout,
которому задать padding, что мы и сделали.

RelativeLayout

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


элементов друг относительно друга. Его детям (но не детям его детей!)
доступно множество разных параметров, layout_alignParentStart и
layout_alignParentEnd — расположить у начала и конца родителя, layout_
toStartOf — теперь name будет занимать всё доступное пространство
от начала родителя до начала price.

8
Селекторы

Как сделать так, чтобы при долгом нажатии на вьюху, её фон менялся
(как и нарисовано в дизайне)? Нужно использовать селекторы —
специального вида drawable-ресурсы, задающиеся в xml.

<selector xmlns:android="http://schemas.android.com/apk/res/
android">
<item android:drawable="@color/colorItemSelected" an-
droid:state_activated="true" />
<item android:drawable="@color/colorItem" />
</selector>

Здесь написано, что в обычном состоянии цвет будет colorItem, а в


активированном (при долгом нажатии) — colorItemSelected.

Стили

В item.xml два текстовых поля, у которых много общего — цвет, размер


и др. Удобно вынести все одинаковые параметры в общий стиль в
styles.xml:

<style name="Text.Item" parent="">


<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:padding">@dimen/margin_large</item>
<item name="android:textColor">@color/colorActive</item>
<item name="android:textSize">@dimen/text_item</item>
</style>

Стили могут наследоваться, parent="" говорит о том, что родителя нет.

9
Полезные ссылки

• Создание списков и карточек


• RelativeLayout
• Изучаем drawable теги
• Темы и стили

10