На презентации Google I/O 15, компания Google представила новую версию библиотеки поддержки которая реализует несколько компонентов, сильно связанных со спецификациями Material Design, среди этих компонентов вы можете найти новые типы ViewGroup такие как AppbarLayout
, CollapsingToolbarLayout
и CoordinatorLayout
.
При правильном комбинировании и настройке данные Viewgroup могут быть очень мощным инструментом, по этому я решил написать статью с некоторыми настройками и советами.
CoordinatorLayout
Как и предполагает его название, цель и философия этой ViewGroup
является координация view элементов, которые находятся внутри него.
Рассмотрим следующую картинку:
В этом примере можем видеть как view элементы размещены друг относительно друга, не прибегая к детальному просмотру, мы видим как одни View
зависят от других. (мы поговорим об этом позже).
Это будет простейшая структура использования CoordinatorLayout
:
<?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" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/background_light" android:fitsSystemWindows="true" > <android.support.design.widget.AppBarLayout android:id="@+id/main.appbar" android:layout_width="match_parent" android:layout_height="300dp" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" android:fitsSystemWindows="true" > <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/main.collapsing" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_scrollFlags="scroll|exitUntilCollapsed" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:expandedTitleMarginStart="48dp" app:expandedTitleMarginEnd="64dp" > <ImageView android:id="@+id/main.backdrop" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:fitsSystemWindows="true" android:src="@drawable/material_flat" app:layout_collapseMode="parallax" /> <android.support.v7.widget.Toolbar android:id="@+id/main.toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:layout_collapseMode="pin" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" android:lineSpacingExtra="8dp" android:text="@string/lorem" android:padding="@dimen/activity_horizontal_margin" /> </android.support.v4.widget.NestedScrollView> <android.support.design.widget.FloatingActionButton android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_margin="@dimen/activity_horizontal_margin" android:src="@drawable/ic_comment_24dp" app:layout_anchor="@id/main.appbar" app:layout_anchorGravity="bottom|right|end" /> </android.support.design.widget.CoordinatorLayout>
Рассмотрим скелет данного layout. У этого CoordinatorLayout
имеется только три дочерних элемента: AppbarLayout
, прокручиваемый view
и закрепленный FloatingActionBar
.
<CoordinatorLayout> <AppbarLayout/> <scrollableView/> <FloatingActionButton/> </CoordinatorLayout>
AppBarLayout
Проще говоря, AppBarLayout
это LinearLayout
на стероидах, их элементы размещены вертикально, с определенными параметрами элементы могут управлять их поведением, когда содержимое прокручивается.
Это может прозвучать запутано сначала, но как, — «Лучше один раз увидеть, чем сто раз услышать», к вашему вниманию .gif-пример:
В данном случае AppBarLayout
это синяя view, размещенная под исчезающим изображением, она содержит Toolbar
, LinearLayout
с заголовком и подзаголовком и TabLayout
с несколькими вкладками.
Мы можем управлять поведением элементов AppbarLayout
с помощью параметров: layout_scrollFlags
. Значение: scroll
в данном случае присутствует почти во всех элементах view, если бы этот параметр не был указан ни в одном из элементов AppbarLayout, он остался бы неизменным, позволяя прокручиваемому контенту проходить позади него.
Со значением: snap
, мы избегаем попадания в полу-анимационного-состояния, это значит, что анимация всегда скрывает или отображает полный размер view.
LinearLayout
который содержит заголовок и подзаголовок будет всегда отображен при прокручивании вверх, (enterAlways
значение), и TabLayout
будет видим всегда так как на него не установлен ни один флаг.
Как видите настоящая мощь AppbarLayout
определяется должным управлением его флагами прокрутки в определенных view.
<AppBarLayout> <CollapsingToolbarLayout app:layout_scrollFlags="scroll|snap" /> <Toolbar app:layout_scrollFlags="scroll|snap" /> <LinearLayout android:id="+id/title_container" app:layout_scrollFlags="scroll|enterAlways" /> <TabLayout /> <!-- no flags --> </AppBarLayout>
Все эти параметры доступны в документации Google Developers. В любом случае, я рекомендую поиграть с примерами. В конце статьи размещены ссылки на репозитории Github с реализацией примеров.
Флаги AppbarLayout
SCROLL_FLAG_ENTER_ALWAYS
: При использовании флага, view будет прокручиваться вниз не зависимо от других прокручиваемых view.
SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED
: Дополнительный флаг для ‘enterAlways’, который изменяет возвращаемый view к изначально прокручиваемому, при исчезновении высоты.
SCROLL_FLAG_EXIT_UNTIL_COLLAPSED
: При выходе, view будет прокручен до тех пор пока не исчезнет.
SCROLL_FLAG_SCROLL
: Элемент view будет прокручиваться в направлении события прокрутки.
SCROLL_FLAG_SNAP
: В конце прокрутки, если view видим только частично, он будет докручен до его ближайшего края.
CoordinatorLayout Behaviors
Проведем не большой эксперимент, запустим Android Studio (>= 1.4) и создадим проект из шаблона: Scrolling Activity, ничего не изменяя, компилирием и вот что мы видим:
При рассмотрении сгенерированного кода, ни макеты layout ни java классы не имеют ничего относящегося к маштабированию анимации плавающей кнопки при прокрутке. Почему?
Ответ находится в исходном коде FloatingActionButton
, с тех пор как Android Studio v1.2 включает java декомпилятор, с помощью ctrl/cmd + click
мы можем проверить исходный код и посмотреть что происходит:
/* * Copyright (C) 2015 The Android Open Source Project * * Floating action buttons are used for a * special type of promoted action. * They are distinguished by a circled icon * floating above the UI and have special motion behaviors * related to morphing, launching, and the transferring anchor point. * * blah.. blah.. */ @CoordinatorLayout.DefaultBehavior( FloatingActionButton.Behavior.class) public class FloatingActionButton extends ImageButton { ... public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> { private boolean updateFabVisibility( CoordinatorLayout parent, AppBarLayout appBarLayout, FloatingActionButton child { if (a long condition) { // If the anchor's bottom is below the seam, // we'll animate our FAB out child.hide(); } else { // Else, we'll animate our FAB back in child.show(); } } } ... }
За маштабирование анимации отвечает новый элемент, представленый вместе с design library, под названием Behavior
. В данном случае CoordinatorLayout.Behavior<FloatingAcctionButton>
, который зависит от некоторых факторов включая прокрутку, показывать FAB или нет, интересно, не правда ли?
SwipeDismissBehavior
Продолжим углубление в код, если вы посмотрите внутрь пакета виджетов design support library, то сможете найти открытй клас под названием: SwipeDismissBehavior
. С этим новым Behavior
мы можем очень легко реализовать функцию свайп для отмены в наших шаблонах с CoordinatorLayout
:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_swipe_behavior); mCardView = (CardView) findViewById(R.id.swype_card); final SwipeDismissBehavior<CardView> swipe = new SwipeDismissBehavior(); swipe.setSwipeDirection( SwipeDismissBehavior.SWIPE_DIRECTION_ANY); swipe.setListener( new SwipeDismissBehavior.OnDismissListener() { @Override public void onDismiss(View view) { Toast.makeText(SwipeBehaviorExampleActivity.this, "Card swiped !!", Toast.LENGTH_SHORT).show(); } @Override public void onDragStateChanged(int state) {} }); LayoutParams coordinatorParams = (LayoutParams) mCardView.getLayoutParams(); coordinatorParams.setBehavior(swipe); }
Custom Behaviors
Создать свой шаблон поведения (Behavior) не так и сложно как может показаться, для начала мы должны принять во внимание несколько основных элементов: child и dependency.
Child и dependency
child это элемент который усиливает поведение, dependency — тот кто будет обслуживать его как тригер для взаимодействия с child элементом. Посмотрим на пример, child — элемент ImageView, а dependency это Toolbar, таким образом, если Toolbar движется, ImageView тоже движется.
Теперь, когда мы определили концепт, можем поговорить о реализации, первым шагом будет наследование от: CoordinatorLayout.Behavior<T>
, значение T
будет класс который принадлежит view, что необходим нам для координации, в данном случае ImageView, после чего мы должны переопределить следующие методы:
- layoutDependsOn
- onDependentViewChanged
Метод: layoutDependsOn
будет вызван каждый раз когда что-то случится в layout, чтобы вернуть true
, как только мы определили dependency, в примере, этот метод срабатывает автоматически при прокручивании (т.к. Toolbar
будет двигаться), таким образом, мы можем подать знак нашему child отреагировать соответствующим образом.
@Override public boolean layoutDependsOn(
CoordinatorLayout parent, CircleImageView, child, View dependency) { return dependency instanceof Toolbar; }
Всякий раз когда layoutDependsOn
возвращает true
будет вызван второй onDependentViewChanged
. Вот тут-то мы и должны реализовать нашу анимацию, перевод или движения всегда зависят от предоставленной зависемости.
public boolean onDependentViewChanged(
CoordinatorLayout parent, CircleImageView avatar, View dependency) {
modifyAvatarDependingDependencyState(avatar, dependency); } private void modifyAvatarDependingDependencyState( CircleImageView avatar, View dependency) { // avatar.setY(dependency.getY()); // avatar.setBlahBlat(dependency.blah / blah); }
А теперь все вместе:
public static class AvatarImageBehavior extends
CoordinatorLayout.Behavior<CircleImageView> { @Override public boolean layoutDependsOn(
CoordinatorLayout parent, CircleImageView, child, View dependency) { return dependency instanceof Toolbar; }
public boolean onDependentViewChanged(
CoordinatorLayout parent, CircleImageView avatar, View dependency) {
modifyAvatarDependingDependencyState(avatar, dependency); } private void modifyAvatarDependingDependencyState( CircleImageView avatar, View dependency) { // avatar.setY(dependency.getY()); // avatar.setBlahBlah(dependency.blah / blah); } }
Дополнительные материалы
Пример Coordinator Behavior — Github
Примеры Coordinator — Github
Введение в coordinator layout на Android — Grzesiek Gajewski
ссылка на оригинал статьи http://habrahabr.ru/post/270121/
Добавить комментарий