Овладение Coordinator Layout

от автора

На презентации Google I/O 15, компания Google представила новую версию библиотеки поддержки которая реализует несколько компонентов, сильно связанных со спецификациями Material Design, среди этих компонентов вы можете найти новые типы ViewGroup такие как AppbarLayout, CollapsingToolbarLayout и CoordinatorLayout.

При правильном комбинировании и настройке данные Viewgroup могут быть очень мощным инструментом, по этому я решил написать статью с некоторыми настройками и советами.

CoordinatorLayout

Как и предполагает его название, цель и философия этой ViewGroup является координация view элементов, которые находятся внутри него.

Рассмотрим следующую картинку:

image

В этом примере можем видеть как 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-пример:

image

В данном случае 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, ничего не изменяя, компилирием и вот что мы видим:

image

При рассмотрении сгенерированного кода, ни макеты 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:

image

Посмотреть код

@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.

image

Child и dependency

child это элемент который усиливает поведение, dependency — тот кто будет обслуживать его как тригер для взаимодействия с child элементом. Посмотрим на пример, child — элемент ImageView, а dependency это Toolbar, таким образом, если Toolbar движется, ImageView тоже движется.

image

Теперь, когда мы определили концепт, можем поговорить о реализации, первым шагом будет наследование от: 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *