Данная статья является переводом статьи Emrullah Luleci, а также её продолжения.
Нижний экран (Здесь и далее под «нижним экраном/слоем» будет подразумеваться элемент bottom sheet — прим. пер.) — компонент, выезжающий снизу экрана, который используется для отображения дополнительного контента. Подробнее об этом элементе можно узнать на официальной сайте посвященном материальному дизайну.

Зависимости
Для использования этого элемента, добавьте последние версии библиотек поддержки в свой проект:
dependencies {
//замените X.X.X номером последней версии
compile 'com.android.support:appcompat-v7:X.X.X'
compile 'com.android.support:design:X.X.X'
}
Создайте класс наследник от AppCompatActivity:
public class ButtonActivity extends AppCompatActivity {
...
}
Создание макетов
Содержимое нижнего экрана
Для удобства воспользуемся макетами. Назовем файл с нижним слоем bottom_sheet.xml.
bottom_sheet.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="340dp"
android:background="@android:color/darker_gray"
android:orientation="vertical"
app:behavior_hideable="true"
app:behavior_peekHeight="80dp"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior">
<TextView
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="@color/colorAccent"
android:gravity="center"
android:text="@string/bottom_sheet_peek"
android:textColor="@android:color/white" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/bottom_sheet_content"
android:textColor="@android:color/white" />
</LinearLayout>
behavior_peekHeight: Определяет высоту видимой части.
behavior_hideable: Определяет, может ли нижний экран скрываться свайпом вниз.
Container view
Создайте CoordinatorLayout в качестве корневого вью. Добавьте в него прямым наследником bottom_sheet.xml. Элементы app_bar и activity_bottom_sheet_content не имеют прямого отношения к нижнему экрану, поэтому их можно заменить или удалить.
Макет
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.androidsample.BottomSheetActivity">
<!-- подключение элемента app bar -->
<include layout="@layout/app_bar" />
<!-- подключение основного контента -->
<include layout="@layout/activity_bottom_sheet_content" />
<!-- подключение нижнего экрана -->
<include layout="@layout/bottom_sheet" />
</android.support.design.widget.CoordinatorLayout>
На данном этапе нижний экран должен работать примерно так:

Динамическое управление
Поведением и свойствами нижнего экрана можно также управлять динамически с помощью Java.
Спойлер
// получение вью нижнего экрана
LinearLayout llBottomSheet = (LinearLayout) findViewById(R.id.bottom_sheet);
// настройка поведения нижнего экрана
BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(llBottomSheet);
// настройка состояний нижнего экрана
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
// настройка максимальной высоты
bottomSheetBehavior.setPeekHeight(340);
// настройка возможности скрыть элемент при свайпе вниз
bottomSheetBehavior.setHideable(false);
// настройка колбэков при изменениях
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
Прикрепление элементов к нижнему экрану
Также можно прикрепить вью к нижнему экрану, чтобы прикрепленный элемент перемещался одновременно с нижним слоем.

Добавим Floating Action Button в макет созданный выше. Новый компонент должен являться непосредственным наследником CoordinatorLayout также как и bottom_sheet. Для прикрепления элемента к нижнему экрану необходимо добавить app:layout_anchor с id вью нижнего экрана, а также app:layout_anchorGravity со значением top|end.
Макет
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.androidsample.BottomSheetActivity">
<!-- подключение элемента app bar -->
<include layout="@layout/app_bar" />
<!-- подключение основного контента -->
<include layout="@layout/activity_bottom_sheet_content" />
<!-- подключение нижнего экрана -->
<include layout="@layout/bottom_sheet" />
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_vertical_margin"
android:src="@drawable/ic_add_shopping_cart_white_24dp"
android:theme="@style/PrimaryActionButton"
app:layout_anchor="@+id/bottom_sheet"
app:layout_anchorGravity="top|end" />
</android.support.design.widget.CoordinatorLayout>
Теперь плавающая кнопка закреплена в верхнем углу нашего нижнего экрана и перемещается вместе с ним.
Скрытие плавающей кнопки при скроле
Для скрытия кнопки при скроле необходимо добавить слушатель к нижнему экрану и отображать/скрывать кнопку. Для начала найдем необходимые вью:
Спойлер
fab = findViewById(R.id.fab);
View llBottomSheet = findViewById(R.id.bottom_sheet);
// настройка поведения нижнего экрана
BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(llBottomSheet);
Если хотите, чтоб кнопка масштабировалась во время скрола, используйте это:
// настройка колбэков при изменениях
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
fab.animate().scaleX(1 - slideOffset).scaleY(1 - slideOffset).setDuration(0).start();
}
});
Для скрытия кнопки в момент начала скрола и отображения после полного сворачивания нижнего экрана, используйте следующее:
Спойлер
// настройка колбэков при изменениях
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
// этот код скрывает кнопку сразу же
// и отображает после того как нижний экран полностью свернется
if (BottomSheetBehavior.STATE_DRAGGING == newState) {
fab.animate().scaleX(0).scaleY(0).setDuration(300).start();
} else if (BottomSheetBehavior.STATE_COLLAPSED == newState) {
fab.animate().scaleX(1).scaleY(1).setDuration(300).start();
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
Результат обоих вариантов можно увидеть ниже:

Вот и всё!
Поделиться с друзьями
Комментарии (7)
ittakir
05.09.2016 16:57В новом мобильном 2ГИС есть такая панелька.
Бесит неимоверно тем, что анимацию сдвига они считают самостоятельно и короткое резкое движение пальцем ни к чему не приводит (панелька прыгает на пару пикселей и возвращается назад).
Хотя, если она будет работать также, как и весь остальной UI Android (верхняя панель, разблокировка экрана), то может и нормально будет.andreich
06.09.2016 11:13боюсь, что в 2гис они полностью все сами писали, потому что там QT используется
izzholtik
А как предполагается доставать этот элемент после полного задвигания под панель навигации, которое видно в конце гифок?
GreenNick
Думаю, что програмно или по какому-нибудь событию
ArtiomCX75
Можно программно установить состояние нижнего экрана, например:
Возможные варианты: STATE_COLLAPSED, STATE_EXPANDED, STATE_HIDDEN.
Agrass
Автор заботливо оставил комментарий, я думаю если не указывать этот параметр, то шторка будет открываться во весь экран :)
StanZakharov
Из-за этого параметра в коде шторка не закрывается полностью. Поэтому его вообще лучше не юзать.