ݺߣ

ݺߣShare a Scribd company logo
Разбираемся с
CoordinatorLayout
Солодовников Сергей
Rambler&Co
План доклада
2
1. Material Design и CoordinatorLayout
2. Знакомство с Behavior
3. Устройство CoordinatorLayout
Принципы
Material Design
3
● Тактильные поверхности
В Material Design интерфейс складывается из так
называемой «цифровой бумаги». Эти слои
расположены на разной высоте и отбрасывают тени
друг на друга.
Принципы
Material Design
4
● Полиграфический дизайн Основополагающие элементы - сетка, пространство,
масштаб, цвет и тд.
Принципы
Material Design
5
● Осмысленная анимация Анимация используется для того, чтобы дать
пользователю подсказки о работе интерфейса
Подключаем CoordinatorLayout к
проекту
6
● compile 'com.android.support:design:24.2.1'
Подключить CoordinatorLayout к проекту просто. Достаточно прописать новую зависимость в build.gradle
разрабатываемого проекта:
Использование атрибута “layout_anchor”
7
<android.support.design.widget.CoordinatorLayout>
<TextView
android:id="@+id/second"
android:layout_gravity="bottom"
android:text="World!"
app:layout_anchorGravity="bottom"
app:layout_anchor="@+id/first"/>
<FrameLayout>
<TextView
android:id="@+id/first"
android:text="Hello!" />
</FrameLayout>
</android.support.design.widget.CoordinatorLayout>
Использование атрибута “layout_anchor”
8
<android.support.design.widget.CoordinatorLayout>
<TextView
android:id="@+id/second"
android:layout_gravity="bottom"
android:text="World!"
app:layout_anchorGravity="bottom"
app:layout_anchor="@+id/first"/>
<FrameLayout>
<TextView
android:id="@+id/first"
android:text="Hello!" />
</FrameLayout>
</android.support.design.widget.CoordinatorLayout>
9
Использование атрибута “layout_anchor”
ViewCompat.animate(OUR_FRAME_LAYOUT)
.translationX(500)
.setDuration(2000L);
10
Использование атрибута “layout_anchor”
Использование атрибута
“layout_anchor”
11
Behavior
12
Создание Behavior
13
public class FancyBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
}
/**
* Конструктор для создания экземпляра FancyBehavior через код.
*/
public FancyBehavior() {
}
/**
* Конструктор для создания экземпляра FancyBehavior через разметку.
*/
public FancyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
// Извлекаем любые пользовательские атрибуты
// в идеале с префиксом behavior_, чтобы обозначить принадлежность атрибута к Behavior
}
Подключение Behavior через xml
14
<TextView
xmlns:app="http://schemas.android.com/apk/res-auto"
app:layout_behavior="ru.rambler.coordinatorlayoutsample.FancyBehavior"
app:behavior_cookies="true"
app:behavior_cookies_size="100dp"/>
● Behavior будет создан с помощью конструктора, который принимает
Context и AttributeSet.
● Behavior может извлечь параметры behavior_cookies и
behavior_coolies_size из AttributeSet
Подключение Behavior через xml
15
app:layout_behavior="@string/appbar_scrolling_view_behavior"
● Содержит в себе путь к ScrollingViewBehavior
Подключение Behavior через код
16
FancyBehavior fancyBehavior = new FancyBehavior();
CoordinatorLayout.LayoutParams params = getView().getLayoutParams();
params.setBehavior(fancyBehavior);
● Можем инициализировать с помощью любого доступного конструктора.
Автоматическое подключение Behavior
17
@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout
● Behavior будет создан с помощью пустого конструктора.
● Если в разметке указать другой Behavior, то он переопределит
DefaultBehavior.
Возможности Behavior
18
● Перехват касаний;
○ onInterceptTouchEvent() и onTouchEvent()
● Перехват Window Insets;
○ onApplyWindowInsets()
● Перехват Measure и Layout;
○ onMeasureChild() и onLayoutChild()
● Перехват NestedScroll;
● Построение зависимостей между View;
● Сохранение состояния Behavior;
Перехват касаний
19
<CoordinatorLayout>
<TextView
android:layout_marginTop="200dp"
android:text="TextView2" />
<TextView
android:layout_marginTop="300dp"
android:text="TextView3"/>
<TextView
android:layout_marginTop="100dp"
android:text="TextView1"
app:layout_behavior="Behavior" />
</CoordinatorLayout>
public class Behavior extends CoordinatorLayout.Behavior {
@Override
public float getScrimOpacity(CoordinatorLayout parent, View child) {
return 0.4f;
}
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child,
MotionEvent ev) {
return true;
}
}
Перехват касаний
20
Зависимости между View
21
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View
dependency) {
if (dependency instanceof Snackbar.SnackbarLayout) {
updateFabTranslationForSnackbar(parent, child);
}
return false;
}
Зависимости между View
22
<FloatingActionButton
app:layout_dodgeInsetEdges="bottom"/>
<SnackbarLayout
app:layout_insetEdge="bottom"/>
Перехват nested scroll
23
1. Каждая View внутри CoordinatorLayout может получить Nested scroll
события.
1. Behavior может перехватывать Nested scroll события от любых View,
как бы глубоко в иерархии они не находилась.
1. Помимо простого Nested Scroll мы можем перехватывать Nested
Fling.
Перехват nested scroll
24
<CoordinatorLayout>
<CoordinatorLayout>
<NestedScrollView />
</CoordinatorLayout>
</CoordinatorLayout>
Nested Scroll события будут перехвачены
только одним CoordinatorLayout
<CoordinatorLayout>
<CoordinatorLayout>
<ScrollView />
</CoordinatorLayout>
</CoordinatorLayout>
Никакое событие прокрутки перехвачено не
будет
Перехват nested scroll
25
1. onStartNestedScroll() - начало прокрутки. Должны вернуть true, чтобы
получить дальнейшие события.
2. onNestedPreScroll() - вызывается перед тем как прокручиваемая View
получила событие прокрутки.
3. onNestedScroll() - вызывается как только прокручиваемая View была
прокручена.
4. onStopNestedScroll() - вызывается в самом конце, что означает конец
текущей прокрутки.
Перехват nested scroll
26
1. onStartNestedScroll() - начало прокрутки. Должны вернуть true, чтобы
получить дальнейшие события.
2. onNestedPreFling() - вызывается перед тем как прокручиваемая View
получила событие fling прокрутки.
3. onNestedFling() - вызывается как только прокручиваемая View была
прокручена.
4. onStopNestedScroll() - вызывается в самом конце, что означает конец
текущей прокрутки.
Перехват nested scroll
27
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View
directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int
dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
ViewCompat.animate(child)
.translationY(child.getHeight())
.setDuration(SCROLL_TIME)
.setInterpolator(interpolator);
}
Доступные Behavior
28
● ScrollingViewBehavior - добавляет offset, чтобы View поместилось под
AppBarLayout.
● SwipeDismissBehavior - добавляет жест “смахнуть”.
● BottomSheetBehavior - любая View приобретает поведение
BottomSheet.
Устройство CoordinatorLayout
29
Устройство CoordinatorLayout
30
Направленный ациклический граф
1. Итерируем по всем вложенным View,
выстраиваем зависимости между ними;
2. Получаем отсортированные View из графа;
3. Переворачиваем список;
4. PROFIT!
Устройство CoordinatorLayout
31
Поиск в глубину (DFS)
1. Итерируем по вершинам графа;
2. Если вершина уже была пройдена, то
игнорируем её;
3. Итерируем по всем вершинам, от которых
идёт путь к рассматриваемой вершине,
если они есть. Это те View, которые
зависят. Выполняем для них пункты 1-4;
4. Добавляем вершину в список
Устройство CoordinatorLayout
32
● Перестроение графа и сортировка View происходит после каждого
вызова метода onMeasure().
● При каждом вызове слушателя ViewTreeObserver.OnPreDrawListener
вызывается метод onChildViewsChanged(EVENT_PRE_DRAW).
● При каждом вызове метода onChildViewRemoved() слушателя
OnHierarchyChangeListener(), вызывается метод
onChildViewsChanged(EVENT_VIEW_REMOVED).
● Метод onChildViewsChanged() итерирует по всем View и вызывает
соответствующие методы у Behavior.
Выводы
33
● CoordinatorLayout с помощью Behavior позволяет изменять поведение
вложенных View без наследования от них а также строить
зависимости между View.
● Support library содержит набор готовых Behavior.
● Behavior у View можно менять “на лету” во время работы.
● CoordinatorLayout не замена FrameLayout.
Спасибо за внимание!
34

More Related Content

Разбираемся с CoordinatorLayout

Editor's Notes

  • #5: используется подход из традиционного графического дизайна: например, журнального и плакатного.
  • #6: В реальном мире предметы не возникают из ниоткуда и не исчезают в никуда — такое бывает только в кино. Поэтому в Material Design мы всё время думаем о том, как с помощью анимации в слоях и в «цифровых чернилах» давать пользователям подсказки о работе интерфейса. Можно сказать, что этот принцип является причиной появления CoordinatorLayout, так как в основе этого компонента лежит идея координации всех View внутри него. Возможно именно поэтому CoordinatorLayout размещается в пакете android.support.design (наряду с AppBarLayout, SnackBar, FAB и тд), а не в android.support:support-v4.
  • #8: Начнём с простого примера. У нас есть CoordinatorLayout, внутри которого находится две View (разметка на слайде упрощена упрощена, чтобы поместиться на слайд). Мы хотим, чтобы TextView(first) следовало бы позади TextView(second), если TextView(second) изменяет своё местоположение. Реализовать это возможно с помощью указания нескольких атрибутов в разметке.
  • #9: Начнём с простого примера. У нас есть CoordinatorLayout, внутри которого находится две View (разметка на слайде упрощена упрощена, чтобы поместиться на слайд). Мы хотим, чтобы TextView(first) следовало бы позади TextView(second), если TextView(second) изменяет своё местоположение. Реализовать это возможно с помощью указания нескольких атрибутов в разметке.
  • #10: Через код создадим анимацию FrameLayout, внутри которого лежит TextView(second)
  • #11: На слайде представлено итоговое поведение. Просто, не правда ли? Всего пару строчек через XML. Отмечу, что неважен порядок View внутри CoordinatorLayout при использовании layout_anchor. Сам CoordinatorLayout сортирует View внутри себя таким образом, чтобы onLayout у зависымых View вызывались после главных View. Подробнее я расскажу позже.
  • #12: Вам всем наверняка известен дизайнерский паттерн, который изображён на слайде. Когда Floating Action Button “прикрепляется” к границе Toolbar или любой другой View. Это легко решаемо при помощи CoordinatorLayout.
  • #13: Если не вдаваться в подробности реализации CoordinatorLayout, то визуально он отображает вложенные View, как FrameLayout. Но у CoordinatorLayout.LayoutParams есть важное отличие в виде behavior. Для чего он нужен? Во время разработки приложений, часто возникают ситуации, когда одна View должна отреагировать на изменение состояния другой View. Именно для перехвата таких изменений и был создан Behavior. Можно сказать, что это основа CoordinatorLayout.
  • #14: Чтобы создать свой Behavior, необходимо унаследоваться от CoordinatorLayout.Behavior, указав тип View к которому можно применить этот Behavior. Можно оставить просто View, тогда он будет применим ко всем View вашего приложения. Далее, у любого behavior должно быть как минимум 2 конструктора: Конструктор, который мы будем использовать при инициализации FancyBehavior. Мы можем передать любое количество аргументов. Конструктор, который будет использоваться при инициализации FancyBehavior через xml разметку. В этом случае, у конструктора должно быть 2 аргумента - Context и AttributeSet. Мы можем извлечь атрибуты, указанные в xml, по аналогии с конструктором View. Стоит упомянуть, что кастомные атрибуты принято начинать с префикса behavior_, по аналогии с layout_, указывая что этот атрибут обрабатывается Behavior, а не самой View Далее рассмотрим как подключить наш FancyBehavior к View..
  • #15: Чаще всего вы будете подключать behavior к View именно через xml. На слайде показан пример такого подключения и запись двух аргументов, которые будет использованы при инициализации FancyBehavior. Наверняка многие из вас видели запись типа app:layout_behavior="@string/appbar_scrolling_view_behavior", этот строковый ресурс всего-лишь содержит в себе путь до ScrollingViewBehavior
  • #16: Чаще всего вы будете подключать behavior к View именно через xml. На слайде показан пример такого подключения и запись двух аргументов, которые будет использованы при инициализации FancyBehavior. Наверняка многие из вас видели запись типа app:layout_behavior="@string/appbar_scrolling_view_behavior", этот строковый ресурс всего-лишь содержит в себе путь до ScrollingViewBehavior
  • #17: Для того, чтобы подключить наш behavior через код, нужно получить LayoutParams у View, при этом тип обязательно должен быть CoordinatorLayout.LayoutParams и вызвать у него метод setBehavior. Если помните, когда мы указывали anchor, то указывали префикс layout_. Дело в том, что поведение View внутри CoordinatorLayout зависит от LayoutParams этой View. Поэтому подключить behavior для View, которая не пренадлежит напрямую CoordinatorLayout, не получится. Важно помнить это при разработки разметки вашего приложения.
  • #18: Если вы создаёте свою View, которой нужен свой Behavior (как было в случае с большинством компонентов в Design Library), тогда вы скорее всего захотите подключить Behavior по умолчанию, без постоянного ручного подключения через код или XML. Чтобы сделать это, нужно лишь добавить простую аннотацию сверху класса вашей View.
  • #19: С помощью Behavior мы можем перехватить и обработать основные события у View, к которой подключён Behavior. Перехват касаний. Используется, например в SwipeDismissBehavior. Происходит по аналогии с ViewGroup. Если вернуть true в методе blocksInteractionBelow, то любое касание к этой View будет игнорироваться. Скорее всего вы захотите как-то визуально показать, что взаимодействие заблокировано – вот почему стандартный функционал blocksInteractionBelow() зависит от значения getScrimOpacity(). Возвращая значение не равное нулю, разом рисуем цвет (getScrimColor(), чёрный по умолчанию) поверх View и отключаем взаимодействия касанием. Перехват Window Insets. Если вы хоть раз отображали контент за системными барами (status/navigation), то вам знаком атрибут fitSystemWindows и Window Insets. С помощью метода onApplyWindowInsets можно изменить итоговые Insets. Метод будет вызываться всякий раз, как Windows Insets изменяются Перехват Measure и Layout. Когда у CoordinatorLayout вызываются методы onMeasure или onLayout, сперва вызываются соответствующие методы у Behavior. И если в них вернуть true, то можно полностю переопределить стандартное поведение. Преположим, нам нужно чтобы с заданным Behavior ширина View не была больше какого-то значение. Это легко сделать с помощью метода onMeasureChild, не меняя атрибуты самой View
  • #20: На слайде показан пример использования getScrimColor. Есть 3 View, на каждую можно кликнуть. К одной из них подключён Behavior с переопределёнными методами.
  • #21: На слайде показан пример использования getScrimColor. Есть 3 View, на каждую можно кликнуть. К одной из них подключён Behavior с переопределёнными методами.
  • #22: Простой случай зависимостей между View мы уже рассматривали, когда использовали атрибут anchor. Но что если нам нужно, чтобы положение View изменилось, если отобразилось другое View? Классический пример вы видели много раз. Показываем Snackbar и FAB автоматически приподнимается. И может быть, вы заметили, что это не работает, если FAB находится не в CoordinatorLayout. Всё дело в FloatingActionButton.Behavior. Методы layoutDependsOn и onDependentViewChanged вызываются очень часто, не стоит инициализировать в них объекты. Сразу оговорюсь, что приведённый код был актуален до 24.2.1. Сейчас всё стало несколько гибче. В каждый CoordinatorLayout.LayoutParams были добавлены два новых атрибута insetEdge (если у какой-то View установлен равный по значению dodgeInsetEdges, то они будут сдвинуты) и dodgeInsetEdges, которые принимают в себя Gravity и можно задать через xml или код.
  • #23: Простой случай зависимостей между View мы уже рассматривали, когда использовали атрибут anchor. Но что если нам нужно, чтобы положение View изменилось, если отобразилось другое View? Классический пример вы видели много раз. Показываем Snackbar и FAB автоматически приподнимается. И может быть, вы заметили, что это не работает, если FAB находится не в CoordinatorLayout. Всё дело в FloatingActionButton.Behavior. Методы layoutDependsOn и onDependentViewChanged вызываются очень часто, не стоит инициализировать в них объекты. Сразу оговорюсь, что приведённый код был актуален до 24.2.1. Сейчас всё стало несколько гибче. В каждый CoordinatorLayout.LayoutParams были добавлены два новых атрибута insetEdge (если у какой-то View установлен равный по значению dodgeInsetEdges, то они будут сдвинуты) и dodgeInsetEdges, которые принимают в себя Gravity и можно задать через xml или код.
  • #24: В саппорт библиотеке есть классы, реализующие интерфейс NestedScrollingChild (NestedScrollView, RecyclerView), которые посылают nested scroll события первому родителю, который реализует интерфейс NestedScrollingParent. Поэтому CoodinatorLayot способен перехватывать такие события. Как только событие поступает к CoordinatorLayout, вызваются соотвнетствующие методы у Behavior. Стоит помнить, что обычное скролирование (ScrollView) не тоже самое, что Nested Scroll и CoordinatorLayout не сможет их передать в Behavior.
  • #25: Стоит отметить некоторые нюансы. Стандартно, View, реализующая интерфейс NestedScrollngChild использует NestedScrollingChildHelper, который отсылает события только для первого NestedScrollngParent. Поэтому стоит избегать вложенные CoordinatorLayout. Второй нюанс в том, что не стоит путать обычное скроллирование, например ScrollView и Nested Scroll. Обычное скроллирование не перехватывается с помощью CoordinatorLayout.
  • #28: Для чего же всё это нужно? Для довольно часто теперь используемого скрытия View при скролировании. FAB или BottomNavigation как на слайде. На сайте приведён пример кода, реализующий такое поведение.
  • #29: В Support Library уже есть набор Behavior, который можно использовать
  • #32: На выходе в начале списка окажутся зависимые View и только затем View, от которых зависят. Но так как важно, чтобы onLayout у View от которых зависят, выполнился вначале, мы переворачиваем этот список.
  • #33: Стоит отметить основные методы, в которых происходит “волшебство” CoordinatorLayout. Полная перестройка графа происходит всякий раз, как CoordinatorLayout вызывает метод onMeasure(). Каждый раз, когда срабатывает слушатель OnPreDrawListener происходят вызовы методов у Behavior, поэтому, как я говорил ранее, не нужно производить инициализацию в них. onChildViewsChanged() основной метод, на который стоит обратить внимание, именно он вызывает эти методы.