Содержание статьи
В прошлой статье мы рассматривали компоненты и виджеты библиотеки совместимости от Google, позволяющие «старым» (до 5.0) версиям Android прикидываться молодыми и стильными, примеряя меха Material Design'а. Однако, как ты уже знаешь, «корпорация добра» не сразу осчастливила программистов своими библиотеками и долгое время эта ниша оставалась незаполненной. Разумеется, нашлись добрые программисты, предложившие свои варианты реализации Material-компонентов для всех версий Android в соответствии с гайдлайнами Google. Сегодня мы рассмотрим некоторые опенсорсные проекты, проверенные временем, разработчиками и, конечно, пользователями приложений.
MaterialDrawer
Ссылка: https://github.com/mikepenz/MaterialDrawer
Автор: Mike Penz
MinSdkVersion: 10
Начнем, пожалуй, с самой известной среди Android-разработчиков библиотеки — MaterialDrawer. Как ясно из названия, она реализует виджет Navigation Drawer, и делает это просто и элегантно (смотри рисунок 1):
drawerResult = new Drawer()
.withActivity(this)
.withToolbar(toolbar)
.withActionBarDrawerToggle(true)
.withHeader(R.layout.drawer_header)
.addDrawerItems(
new PrimaryDrawerItem().withName("Элемент 1").withIcon(FontAwesome.Icon.faw_home).withBadge("99").withIdentifier(1),
new PrimaryDrawerItem().withName("Элемент 2").withIcon(FontAwesome.Icon.faw_gamepad),
…
new SectionDrawerItem().withName("Новая секция"),
new SecondaryDrawerItem().withName("Элемент 3").withIcon(FontAwesome.Icon.faw_cog),
new SecondaryDrawerItem().withName("Элемент 4").withIcon(FontAwesome.Icon.faw_question).setEnabled(false),
…
new DividerDrawerItem(), // Разделитель
new SecondaryDrawerItem().withName("Элемент 5").withIcon(FontAwesome.Icon.faw_github).withBadge("666+").withIdentifier(2)
)
.build();
Метод withIcon определяет иконку слева, withBadge — тестовую метку справа (здесь, например, удобно отображать число новых сообщений или звонков), а setEnabled управляет доступностью элемента.
Обработка нажатий на элементы меню:
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id, IDrawerItem drawerItem) {
if (drawerItem instanceof Nameable) {
// Выбран обычный элемент меню
}
if (drawerItem instanceof Badgeable) {
// Выбран элемент с текстовой меткой (бейджем)
drawerResult.updateBadge("+1", position); // Меняем значение
}
})
})
Обработкой длинного клика занимается метод withOnDrawerItemLongClickListener с обработчиком onItemLongClick.
В наличии поддержка разных аккаунтов с аватарками и быстрое переключение между ними. Также библиотека может похвастаться нехилым набором иконок для пунктов меню. В репозитории доступен подробнейший пример использования.
FloatingActionButton
Пожалуй, Floating Action Button — наиболее популярный объект реализации в сторонних библиотеках. Учитывая, что неплохая поддержка FAB появилась и у Google, большинство из них потеряло актуальность и перестало развиваться. Мы же рассмотрим библиотеку с функционалом, отсутствующим в Support Library от Google.
Ссылка: https://github.com/futuresimple/android-floating-action-button
Автор: Jerzy Chalupski
MinSdkVersion: 14
В Hangouts при нажатии на кнопку с «+» на заглавном экране открываются дополнительные Action-элементы с подписями. Рассматриваемая библиотека реализует подобный функционал (рисунок 2). Вся «магия» готовится в разметке активности или фрагмента:
<com.getbase.floatingactionbutton.FloatingActionsMenu
android:id="@+id/multiple_actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
fab:fab_addButtonColorNormal="@color/white"
fab:fab_addButtonColorPressed="@color/white_pressed"
fab:fab_addButtonPlusIconColor="@color/half_black"
fab:fab_labelStyle="@style/menu_labels_style">
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/action_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_colorNormal="@color/white"
fab:fab_title="Action A"
fab:fab_colorPressed="@color/white_pressed"/>
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/action_b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_colorNormal="@color/white"
fab:fab_title="Action B"
fab:fab_colorPressed="@color/white_pressed"/>
</com.getbase.floatingactionbutton.FloatingActionsMenu>
Элемент FloatingActionsMenu содержит две вложенные кнопки с подписями слева, появляющиеся при нажатии исходной. В коде же остается написать только обработчики нажатия:
final FloatingActionButton actionA = (FloatingActionButton) findViewById(R.id.action_a);
actionA.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
actionA.setTitle("Action A clicked");
}
});
Доступен также форк этой библиотеки, рассчитанный на устройства с API 4.
Замечу, что данный элемент (дополнительные Action'ы, выпадающие из FAB) пока не является парадигмой Material Design'а, скорее, это эксперимент Google исключительно для Hangouts, что, кстати, было озвучено на Droidcon 2015.
DiscreteSeekBar
Ссылка: https://github.com/AnderWeb/discreteSeekBar
Автор: Gustavo Claramunt
MinSdkVersion: 7
Библиотека реализует компонент Discrete Slider, активно показывающий изменение прогресса в виде «капли» с текстовой меткой (рисунок 3). Для версий Android до 5.0 имитируется эффект «ряби» при нажатии на слайдер. Добавление компонента в разметку тривиально:
<org.adw.library.widgets.discreteseekbar.DiscreteSeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:dsb_min="2"
app:dsb_max="15"
/>
Для написания кода доступны обработчики: onProgressChanged — при изменении прогресса, onStartTrackingTouch — при первом прикосновении, onStopTrackingTouch — при убирании пальца.
sb = (DiscreteSeekBar) view.findViewById(R.id.seekBar);
sb.setOnProgressChangeListener(new DiscreteSeekBar.OnProgressChangeListener() {
@Override
public void onProgressChanged(DiscreteSeekBar discreteSeekBar, int i, boolean b) {}
@Override
public void onStartTrackingTouch(DiscreteSeekBar discreteSeekBar) { }
@Override
public void onStopTrackingTouch(DiscreteSeekBar discreteSeekBar) {}
});
Кроме того, с помощью обширных свойств компонента можно менять внешний вид и поведение. Например, чтобы задать основные цвета (окантовки, капли и «ряби»), достаточно указать в разметке:
app:dsb_indicatorColor="@color/accent"
app:dsb_progressColor="@color/accent"
app:dsb_rippleColor="@color/divider"
Android-swipe-to-dismiss-undo
Ссылка: https://github.com/hudomju/android-swipe-to-dismiss-undo
Автор: Hugo Doménech Juárez
MinSdkVersion: 15
Данная библиотека реализует эффект свайпа для элементов списков ListView и ReсyclerView. После смахивания вместо элемента появляется один или несколько Action'ов, как правило, удаляющих элемент, но с возможностью отмены (рисунок 4).
Сокращенный вариант разметки элемента списка представлен ниже:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout…
…
<LinearLayout>
<!-- Разметка элемента -->
</LinearLayout>
<LinearLayout…
android:orientation="horizontal"
android:visibility="gone"
android:weightSum="3">
<TextView…
android:id="@+id/txt_deleted"
android:gravity="center_vertical"
android:text="@string/deleted">
<TextView…
android:id="@+id/txt_undo"
android:gravity="center"
android:text="@string/undo">
</LinearLayout>
</FrameLayout>
Здесь к разметке добавляется невидимый слой LinearLayout (visibility="gone"
), который появится после свайпа. В данном случае он содержит две тестовые метки — «Удалено» и «Отмена».
Очевидно, для отмены удаления элемента необходимо обработать нажатие на соответствующую метку:
final SwipeToDismissTouchListener<RecyclerViewAdapter> touchListener =
new SwipeToDismissTouchListener<>(
new RecyclerViewAdapter(recyclerView),
new SwipeToDismissTouchListener.DismissCallbacks<RecyclerViewAdapter>() {
@Override
public boolean canDismiss(int position) {
// Любой элемент можно удалить
return true;
}
@Override
public void onDismiss(RecyclerViewAdapter view, int position) {
// Окончательное удаление элемента
adapter.remove(position);
}
});
recyclerView.setOnTouchListener(touchListener);
recyclerView.setOnScrollListener((RecyclerView.OnScrollListener)touchListener.makeScrollListener());
recyclerView.addOnItemTouchListener(new SwipeableItemClickListener(this,
new OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
// Нажали "Отмена"?
if (view.getId() == R.id.txt_undo) {
// Отменяем удаление
touchListener.undoPendingDismiss();
} else {
// Обрабатываем нажатие на элемент списка
Toast.makeText(context, "Position " + position, LENGTH_SHORT).show();
}
}
}));
Метод canDismiss() определяет, может ли быть удален элемент по индексу, а onDismiss() окончательно его удаляет. Для обработки нажатий используется специальный обработчик SwipeableItemClickListener, в методе onItemClick которого отлавливается нажатие на метку «Отмена» с последующим вызовом undoPendingDismiss().
Данная библиотека в силу простоты и лаконичности хорошо подходит для расширения функционала свайпов. Но есть небольшой подводный булыжник — пример из репозитория крашится при использовании RecyclerView, так как не находит обработчик onRequestDisallowInterceptTouchEvent. Фикс тривиален до неприличия:
public class FixSwipeableItemClickListener extends SwipeableItemClickListener {
public FixSwipeableItemClickListener(Context context, com.hudomju.swipe.OnItemClickListener listener){
super(context, listener);
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean b) { }
}
Теперь вместо SwipeableItemClickListener используем приведенный класс:
recyclerView.addOnItemTouchListener(new FixSwipeableItemClickListener(this,…
Возможно, когда ты прочтешь эти строки, данное исправление уже будет в репозитории проекта.
Custom-rating-bar
Ссылка: https://github.com/kanytu/custom-rating-bar
Автор: Pedro Oliveira
MinSdkVersion: 4
RatingBar можно по праву назвать самым печальным виджетом всего Android SDK. Казалось бы, паттерн взаимодействия с пользователем в виде выбора звездочек рейтинга интуитивно понятен, удобен и не требует от пользователя лишних телодвижений вроде необходимости печатать текст на экранной клавиатуре или выбирать из списка подходящий вариант… Но нет, стандартный компонент — это нечто!
Во-первых, он масштабируется чуть менее, чем никак, то есть если, например, вставить его в диалоговое окно, то на смартфоне с диагональю 4.65' в портретном режиме пять звезд уже не влезут и число автоматически уменьшится до четырех, потроллив и пользователя, и разработчика. Во-вторых, как подсказывает Stack Overflow, у RatingBar'а все-таки можно установить style="?android:attr/ratingBarStyleSmall"
, уменьшающий размер звездочек до… точек (!), что также озадачивает. В общем, использовать этот компонент в адаптивной разметке чрезвычайно неудобно, если вообще возможно.
Однако найти сторонние реализации RatingBar'a почему-то не так-то просто. Я остановился на готовом классе (именно классе, не библиотеке) — сustom-rating-bar, который нужно скопировать в проект вручную.
Виджет добавляется в разметку вполне стандартно (рисунок 5):
<com.poliveira.apps.CustomRatingBar
app:maxStars="5"
android:layout_width="match_parent"
android:layout_height="48dp"
android:id="@+id/ratingBar"
app:halfStars="false"/>
Размеры учитываются корректно, и указанная ширина layout_width="match_parent"
гарантирует, что этот RatingBar впишется в отведенное для него место. Использовать также просто:
crb = (CustomRatingBar) dialog.findViewById(R.id.ratingBar);
crb.setOnScoreChanged(new CustomRatingBar.IRatingBarCallbacks() {
@Override
public void scoreChanged(float score) {
// В переменной score содержится выбранное число звезд
}
});
Выбор рейтинга сопровождается анимацией, а в качестве звездочек можно использовать любые растровые изображения.
Кстати, в репозитории автора есть несколько других полезных классов, не пропусти.
Material Dialogs
Ссылка: https://github.com/afollestad/material-dialogs
Автор: Aidan Follestad
MinSdkVersion: 8
Данная библиотека специализируется исключительно на Material-диалогах (рисунок 6). Взору разработчиков представлены самые разнообразные варианты — простые (Basic), с кнопками (Action), со списком (List), с единственным (Single Choice) и множественным (Multi Choise) выбором элементов, с нестандартной разметкой и адаптером (Custom Adapter), с выбором цветов и темой оформления, с круговым и линейным прогрессом (Progress Bar). К сожалению, не хватает диалога выбора даты и времени.
В качестве примера рассмотрим вариант диалога с произвольным (нестандартным) содержимым:
boolean wrapInScrollView = true;
new MaterialDialog.Builder(this)
.title(R.string.title)
.customView(R.layout.custom_view, wrapInScrollView)
.positiveText(R.string.positive)
.callback(new MaterialDialog.ButtonCallback() {
@Override
public void onPositive(MaterialDialog dialog) {}
});
.show();
С помощью метода customView указывается xml-файл с необходимой разметкой (Layout), а логический параметр wrapInScrollView определяет, нужно ли поместить содержимое внутрь ScrollView для скроллинга на маленьком экране смартфона. Очевидно, что при использовании компонентов, изначально поддерживающих скроллинг (ListView или RecyclerView), значение нужно установить в false. Метод positiveText определяет заголовок так называемого положительного действия ("OK", "Yes", "Agree" и так далее), а callback задает обработчик нажатия действия — onPositive. По аналогии используются negativeText (onNegative) и neutralText (onNeutral).
Библиотека постоянно обновляется, в Google Play доступен полный пример.
Material
Ссылка: https://github.com/rey5137/material
Автор: Rey Pham
MinSdkVersion: 9
Выше мы познакомились с отдельными виджетами Material Design'а, теперь пришло время подключить тяжелую артиллерию. Material — библиотека, как говорится, на все случаи жизни. В ней есть все необходимые материальные виджеты — Navigation Drawer, круговые и линейные Progress Bar, разнообразные кнопки (FAB, с окантовкой и без), переключатели (Checkbox, Radio Button, Switch), слайдеры (Continuous, Discrete), выпадающие списки (Spinner), поля ввода с фильтрацией (Textfields), всплывающие информационные панели (Snackbars), разные виды диалогов (Simple, Choice, Multi Choice), поддерживающие нестандартную разметку (Custom Layout), а также компоненты выбора даты и времени. Библиотека поддерживает кастоматизацию всех элементов — цвета, темы, эффекта «ряби» при нажатии, анимации и тому подобного (рисунки 7–8).
Единственное, что откровенно не понравилось, — это слайдеры, которые настолько неохотно реагируют на прикосновения, что отбивают всякое желание их использовать. Но не беда — ранее мы уже упомянули Discrete Seekbar, его и советую взять на замену. Остальные компоненты работают без нареканий. Автор библиотеки на официальном форуме всегда отвечает на вопросы и подсказывает пути решения тех или иных проблем.
Material Design Android Library
Ссылка: https://github.com/navasmdc/MaterialDesignLibrary
Автор: Ivan Navas
MinSdkVersion: 8
Еще одна комплексная библиотека, но с меньшим числом виджетов. В наличии все виды кнопок (с поддержкой анимации), переключатели (Switch), слайдеры (Slider), индикаторы Progress Bar (круговые, линейные), панели Snackbar и простые диалоги (рисунки 9–10). Дополнительно реализован готовый диалог выбора цвета по значениям красного, зеленого, синего (R,G,B).
Если это все, что тебе нужно, — можешь смело использовать данный вариант, особенно для совсем уж старых устройств с Android 2.2 на борту.
Шрифт Roboto
В Material Design широко используется новый шрифт Roboto. Он не входит в библиотеку совместимости, но его можно использовать в предыдущих версиях Android с помощью библиотеки typerlib.
Добавляем в build.gradle зависимость:
dependencies {
compile 'io.github.enzokie:typerlib:1.0.0'
}
Меняем шрифт:
txt = (TextView) findViewById(R.id.textView);
…
txt.setTypeface(Typer.set(this).getFont(Font.ROBOTO_THIN));
txt2.setTypeface(Typer.set(this).getFont(Font.ROBOTO_BLACK));
txt3.setTypeface(Typer.set(this).getFont(Font.ROBOTO_CONDENSED_ITALIC));
На рисунке 11 приведен пример использования нового шрифта. Нужно сказать, что разница не сильно заметна. Кроме того, не у всех элементов (например, стандартного заголовка) таким образом можно сменить шрифт.
Выводы
Сегодня мы рассмотрели ряд полезных библиотек, помогающих вдохнуть толику Material Design в ранние версии Андроида. Наверняка у тебя возник вопрос: стоит ли все это использовать или лучше остановиться на библиотеке совместимости Google? Однозначного ответа, естественно, не существует. Логика подсказывает, что как только тот или иной компонент появится в AppCompat или Design Support Library, необходимость в сторонней библиотеке отпадет сама собой. Впрочем, вряд ли стандартный компонент Navigation Drawer когда-нибудь сравнится по удобству работы с тем же MaterialDrawer Mike Penz. Лично я не вижу ничего криминального в использовании сторонних библиотек при условии, что они не противоречат гайдам Google и здравому смыслу.
WWW
В Google Play существуют приложения-сборники, наглядно демонстрирующие компоненты множества библиотек для разработчиков. Используя подобные сборки, можно сразу же оценить функциональность виджетов без скачивания и компиляции тестовых проектов.
Ищи по названиям — Libraries for Developers, API Demos for Android, Android Libraries.