С момента возникновения концепции Material design одним из самых простых в реализации элементов стала плавающая кнопка действия — FAB, Floating Action Button. Этот элемент быстро обрёл широчайшую популярность среди разработчиков и дизайнеров. В этой публикации мы рассмотрим, как можно анимировать FAB и сделать её интерактивной. Но сначала разберём, как вообще добавить этот элемент в ваш проект.
FAB выглядит как цветной круг в правом нижнем углу экрана. Если в Android Studio создать новый проект Blank Activity, то в нём автоматически будет сгенерирована плавающая кнопка действия.
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_menu_help"
/>
Floating Action Button
FAB может быть одного из двух размеров: 56 dp (по умолчанию) или 40 dp. Если вы хотите подробнее изучить принципы использования FAB в дизайне приложения, то обратите внимание на официальные гайдлайны Google.
В самых свежих Android-приложениях FAB реагирует на прокручивание списка элементов. Было бы логичнее скрывать её во время прокручивания. Вот что имеется в виду:
Для отображения этой анимации создадим
recyclerView
, благодаря которому FAB реагирует на прокручивание. Сегодня доступно немало библиотек, позволяющих добиться этого с помощью пары строк кода. Например:public class FAB_Hide_on_Scroll extends FloatingActionButton.Behavior {
public FAB_Hide_on_Scroll(Context context, AttributeSet attrs) {
super();
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
//child -> Floating Action Button
if (child.getVisibility() == View.VISIBLE && dyConsumed > 0) {
child.hide();
} else if (child.getVisibility() == View.GONE && dyConsumed < 0) {
child.show();
}
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
}
Здесь использован класс
FloatingActionButton.Behavior()
, чья основная задача, согласно официальной документации, заключается в перемещении видов FloatingActionButton
, чтобы ни один из Snackbar
их не перекрывал. Но в нашем случае этот класс является расширенным, так что мы можем его использовать для реализации нужного поведения кнопки.Что же делает данный класс? При каждой инициализации прокрутки вниз метод
onStartNestedScroll()
возвращает значение true. После этого метод onNestedScroll()
отображает или прячет кнопку, в зависимости от её текущей видимости. Конструктор класса FloatingActionButton.Behavior()
является важной частью описанного поведения вида (view) и извлекается из XML-файла. public FAB_Hide_on_Scroll(Context context, AttributeSet attrs) {
super();
}
Добавим в FAB атрибут
layout_behavior
, содержащий название пакета, а в конце — имя класса. Иначе говоря, в атрибуте должно быть указано точное размещение класса в проекте. Например:app:layout_behavior="com.valdio.valdioveliu.floatingactionbuttonproject.Scrolling_Floating_Action_Button.FAB_Hide_on_Scroll"
Анимация выглядит хорошо, но можно сделать ещё лучше. Например, чтобы кнопка уходила за пределы экрана во время прокрутки — это более реалистичное поведение:
Здесь используется та же логика, что и в предыдущем варианте, за исключением способа исчезновения FAB. Анимация довольно проста. Кнопка уходит вниз с помощью LinearInterpolator. Расстояние, которое ей нужно пройти, равно высоте кнопки плюс ширина нижнего поля.
Обратите внимание, что в выражениях
if
отсутствуют проверки View.VISIBLE
и View.GONE
, поскольку в данном случае вид не скрывается, а лишь уплывает за пределы экрана.public class FAB_Float_on_Scroll extends FloatingActionButton.Behavior {
public FAB_Float_on_Scroll(Context context, AttributeSet attrs) {
super();
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
//child -> Floating Action Button
if (dyConsumed > 0) {
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
int fab_bottomMargin = layoutParams.bottomMargin;
child.animate().translationY(child.getHeight() + fab_bottomMargin).setInterpolator(new LinearInterpolator()).start();
} else if (dyConsumed < 0) {
child.animate().translationY(0).setInterpolator(new LinearInterpolator()).start();
}
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
}
Меню из FAB'ов
Существует немало приложений, авторы которых создали красивые и хорошо работающие меню, состоящие из плавающих кнопок действия.
Давайте сделаем нечто подобное. Для начала создадим макет, содержащий три маленькие кнопки. Они невидимы и расположены в самом низу макета, под главной FAB. Содержимое fab_layout.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_menu_compass"
android:visibility="invisible"
app:backgroundTint="@color/colorFAB"
app:fabSize="mini" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_menu_myplaces"
android:visibility="invisible"
app:backgroundTint="@color/colorFAB"
app:fabSize="mini" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_menu_share"
android:visibility="invisible"
app:backgroundTint="@color/colorFAB"
app:fabSize="mini" />
</FrameLayout>
Этот макет нужно включить в макет activity под главной FAB.
<include layout="@layout/fab_layout" />
Теперь нужно добавить анимацию исчезновения и появления каждой из малых кнопок.
Примечание: здесь вы можете столкнуться с проблемой, связанной с отработкой нажатия на малые кнопки. Когда анимация завершается, реальное положение кнопки не меняется, перемещается только вид. Поэтому вы не сможете правильно обработать касание кнопки. Для решения этой проблемы можно настроить параметры макетов каждой кнопки с учётом их нового положения, и только потом выполнять анимацию перемещения вида.
Саму анимацию вы можете посмотреть в конце этой публикации. Порядок действий для всех кнопок один и тот же, различаются лишь координаты перемещения.
Отображение меню:
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) fab1.getLayoutParams();
layoutParams.rightMargin += (int) (fab1.getWidth() * 1.7);
layoutParams.bottomMargin += (int) (fab1.getHeight() * 0.25);
fab1.setLayoutParams(layoutParams);
fab1.startAnimation(show_fab_1);
fab1.setClickable(true);
fab1
перемещается с помощью добавления в layoutParams
полей справа и снизу, после чего инициируется анимация.Скрывание меню:
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) fab1.getLayoutParams();
layoutParams.rightMargin -= (int) (fab1.getWidth() * 1.7);
layoutParams.bottomMargin -= (int) (fab1.getHeight() * 0.25);
fab1.setLayoutParams(layoutParams);
fab1.startAnimation(hide_fab_1);
fab1.setClickable(false);
Процесс скрывания представляет собой обратное воспроизведение предыдущей анимации.
//Анимации одной из малых кнопок
Animation show_fab_1 = AnimationUtils.loadAnimation(getApplication(), R.anim.fab1_show);
Animation hide_fab_1 = AnimationUtils.loadAnimation(getApplication(), R.anim.fab1_hide);
Теперь создадим в папке res/anim/ файлы для каждой из анимаций. Делается это просто, но если у вас возникнут затруднения, то можете обратиться к документации.
Содержимое fab1_show.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<!-- Rotate -->
<rotate
android:duration="500"
android:fromDegrees="30"
android:interpolator="@android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="4"
android:repeatMode="reverse"
android:toDegrees="0"></rotate>
<!--Move-->
<translate
android:duration="1000"
android:fromXDelta="170%"
android:fromYDelta="25%"
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="0%"
android:toYDelta="0%"></translate>
<!--Fade In-->
<alpha
android:duration="2000"
android:fromAlpha="0.0"
android:interpolator="@android:anim/decelerate_interpolator"
android:toAlpha="1.0"></alpha>
</set>
Содержимое fab1_hide.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<!--Move-->
<translate
android:duration="1000"
android:fromXDelta="-170%"
android:fromYDelta="-25%"
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="0%"
android:toYDelta="0%"></translate>
<!--Fade Out-->
<alpha
android:duration="2000"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="0.0"></alpha>
</set>
Если вы посмотрите на тэг перевода (translate tag), отвечающий за движение вида, то увидите, что коэффициент перемещения (170% и 25%) соответствует коэффициентам, использованным при добавлении полей и извлечённым в Java-код.
Все вышеописанные шаги мы повторяем и для остальных малых кнопок. Различаются только коэффициенты перемещения:
fab2
— 150% и 150%, fab3
— 25% и 170%.Результат наших усилий:
Это не конец
Надеюсь, эта статья помогла вам понять, как можно анимировать Floating Action Button в вашем проекте. Дальше вы можете изучить ресурсы, посвящённые анимации в Android, и начать создавать собственные виды анимаций. Весь код описанного в этой публикации проекта доступен на GitHub.
Поделиться с друзьями
Комментарии (7)
Zeliret
24.05.2016 12:10+1Тянуться к экшен бару большим пальцем на текущих гиганстких смартах — та еще мука. Нет уж, пусть любой наиболее востребованный функционал будет снизу. И actionbutton/menu наиболее удобный вариант на текущий момент.
Revertis
> Этот элемент быстро обрёл широчайшую популярность среди разработчиков и дизайнеров
Правильно вы написали — разработчиков и дизайнеров. Юзерам она нафиг не нужна в 90% случаев, а её пихают туда часто «чтобы было, ибо надо по спеке». Для начала надо очень хорошо обдумать функцию, которая активируется по этой кнопке, а не пихать просто так. Если приложение, или экран решает одну конкретную проблему, то можно (не нужно) её сделать, а если там несколько функций, пусть даже с одной чуть более используемой, то лучше пользоваться ActionBar.
Serator
Спецификация (скорее даже пособие по стилю) не гласит располагать этот элемент на всех страницах. Скорее это проблемы отдельных приложений и их разработчиков. +, там есть различные вариации, в том числе обыгрывается ситуация с несколькими значимыми стилями.
Revertis
Я это понимаю, только разработчики клеют эту кнопку везде к месту и не к месту.
matmotex
Как пример — приложение авито. Что бы открыть фильтр поиска, нужно немного прокрутить список вверх объявлений. Лично меня это раздражает. Это можно было сделать обычным контекстом, рядом с иконкой поиска.