Показываем список приложений. Улучшаем внешний вид. ItemDecoration. Отступы. SwipeRefreshLayout.

Урок 13 — Показываем список приложений. Улучшаем внешний вид. ItemDecoration. Отступы. SwipeRefreshLayout.

В прошлом уроке мы научились использовать RecyclerView для отображения списков. Но, согласитесь, выглядит наш список не очень здорово, не так ли?

Как минимум, ему не хватает разделителей.

ItemDecoration

В Android SDK существует класс RecyclerView.ItemDecoration. Он позволяет добавить к ячейкам дополнительные "визуальные эффекты", если можно так выразиться.

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

Первое, что приходит в голову — добавить в лэйаут ячейки тонкий View темного цвета (в самый низ). Но такой способ не эффективен, к тому же, может получиться так, что он будет выглядеть ненативно на каких-то определенных версиях Android.

Правильный вариант — использовать механизм ItemDecoration. Существует специальный класс RecyclerView.DividerItemDecoration, созданный как раз для этих целей. Реализовать его невероятно просто!

В первую очередь, нужно вынести LayoutManager в отдельный объект:

LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);

Зачем? Как вы помните, RecyclerView отображает не только списки. Он создан для отображения коллекций данных, и он может отображать данные, к примеру, сеткой. За отображение данных отвечает LayoutManager.

В нашем случае нужен именно список, а не сетка, поэтому мы используем LinearLayoutManager. LinearLayoutManager умеет отображать не только вертикальные, но и горизонтальные списки. По-умолчанию, он отображает списки вертикально, но так же можно создать его следующим способом:

LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);

И тогда список будет скроллиться горизонтально. А если передать в качестве последнего параметра true, то он будет отображен в обратном направлении.

Так вот, DividerItemDecoration работает со списками и горизонтальной, и вертикальной ориентации, и ему нужно знать, какую ориентацию мы используем. Поэтому при создании передадим ему текущую ориентацию LayoutManager:

DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, layoutManager.getOrientation());

Первый параметр — Context, второй — ориентация.

И просто добавим его в RecyclerView:

recyclerView.addItemDecoration(dividerItemDecoration);

Итого, получаем вот такой onCreate():

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        appManager = new AppManager(this);
        List<AppInfo> installedApps = appManager.getInstalledApps();

        AppsAdapter appsAdapter = new AppsAdapter();

        RecyclerView recyclerView = findViewById(R.id.apps_rv);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);

        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, layoutManager.getOrientation());
        recyclerView.addItemDecoration(dividerItemDecoration);

        recyclerView.setAdapter(appsAdapter);


        appsAdapter.setApps(installedApps);
        appsAdapter.notifyDataSetChanged();
    }

Вуаля — данные в нашем списке стало проще читать!

DividerItemDecoration в RecyclerView
DividerItemDecoration в RecyclerView

ItemDecoration используется не только для этого, и у RecyclerView может быть несколько различных ItemDecoration — есть, например, возможность отслеживать свайпы и drag'n'drop при помощи ItemTouchHelper. Его мы рассмотрим в следующих уроках.

Отступы.

Несмотря на то, что мы добавили разделители, список все ещё выглядит не очень хорошо, правда? Как минимум, стоит добавить отступы к элементам внутри ячейки. В Android есть два типа отступов — padding и margin.

Padding

Padding — это отступ внутри View, то есть отступ от контента до границ View.

Когда мы увеличиваем padding, View в общем случае увеличивается в размерах или уменьшает контент внутри себя. В XML лэйаутах за него отвечают следующие атрибуты:

  • android:padding — устанавливает padding сразу со всех сторон.
  • android:paddingTop — устанавливает padding сверху.
  • android:paddingBottom — устанавливает padding снизу.
  • android:paddingStart — устанавливает padding в начале View (слева на LTR локалях, справа — на RTL).
  • android:paddingEnd — устанавливает padding в конце View (справа на LTR локалях, слева — на RTL).

Margin

Margin — это отступ снаружи View, то есть отступ от границ View до границ контейнера или соседнего View.

Когда мы увеличиваем margin, View в общем случае не меняется в размерах и не уменьшает контент, но при этом увеличивается отступ от соседних View и/или контейнера.

В XML лэйаутах за него отвечают следующие атрибуты:

  • android:layout_margin — устанавливает margin сразу со всех сторон.
  • android:layout_marginTop — устанавливает margin сверху.
  • android: layout_marginBottom — устанавливает margin снизу.
  • android: layout_marginStart — устанавливает margin в начале View (слева на LTR локалях, справа — на RTL).
  • android: layout_marginEnd — устанавливает margin в конце View (справа на LTR локалях, слева — на RTL).

Давайте посмотрим на наше приложение.

Нам определенно нужно увеличить отступы со всех краев ячейки, чтобы контент не прижимался к краям. Тут нам нужен padding.

Измените файл лэйаута ячейки, чтобы он выглядел следующим образом:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:paddingBottom="8dp"
    android:paddingEnd="8dp"
    android:paddingStart="16dp"
    android:paddingTop="8dp">

    <ImageView
        android:id="@+id/icon_iv"
        android:layout_width="72dp"
        android:layout_height="72dp"
        android:adjustViewBounds="true"
        android:scaleType="fitXY" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/name_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/version_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

</LinearLayout>

Мы добавили внутренние отступы со всех сторон, причем слева чуть больше. Уже стало лучше, не так ли?

Padding в ячейках RecyclerView
Padding в ячейках RecyclerView

Однако, текст все равно слишком сильно прижимается к иконке и друг к другу. Нужно добавить отступ для названия приложения слева, и отступ для версии слева и сверху. Поскольку текст у нас находится в отдельном LinearLayout, достаточно просто добавить левый и правый отступ к нему, и верхний отступ к TextView с версией:

<LinearLayout
    android:layout_height="wrap_content"
    android:layout_marginEnd="8dp"
    android:layout_marginStart="8dp"
    android:layout_width="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/name_tv"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content" />

    <TextView
        android:id="@+id/version_tv"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:layout_width="wrap_content" />

</LinearLayout>

Отступы у TextView с версией
Отступы у TextView с версией

Ну и выделим заголовок, увеличив размер текста:

<TextView
    android:id="@+id/name_tv"
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:textSize="18sp" />

Размер текста в TextView
Размер текста в TextView

Куда лучше, чем было, не так ли?

SwipeRefreshLayout

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

Во-первых, нужно дать пользователю возможность вручную обновить данные.

Во-вторых, нужно отслеживать установку и удаление приложений.

О втором пункте мы поговорим в одном из следующих уроков, а вот первый подробно разберем сейчас.

Общепринятый паттерн обновления данных в списках — Pull-to-Refresh, то есть, пользователю нужно потянуть список вниз и отпустить его. Для реализации Pull-to-Refresh в Android долгое время нужно было использовать сторонние библиотеки, пока Google не добавил SwipeRefreshLayout в Support Library.

Чтобы добавить SwipeRefreshLayout, просто замените FrameLayout на него в лэйауте MainActivity:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/swipe_refresh_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.test.packages.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/apps_rv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</android.support.v4.widget.SwipeRefreshLayout>

Теперь запустите приложение, потяните список вниз и отпустите:

SwipeRefreshLayout и RecyclerView
SwipeRefreshLayout и RecyclerView

Как видите, визуально процесс обновления начался. Однако, обновления данных, само собой, не происходит. Чтобы обновление данных заработало, нужно выполнить ещё несколько простых шагов.


Продолжение доступно на платных тарифах

Это недорого — всего от 440 ₽ в месяц!


ВЫБРАТЬ ТАРИФ


Продолжение доступно после регистрации

Все уроки на сайте доступны абсолютно бесплатно после регистрации.

Регистрация займёт меньше минуты ;)