Перехоплюємо все за допомогою CoordinatorLayout Behavior
Ви не просунетеся далеко у вивченні Android Design Support Library, не зіткнувшись з CoordinatorLayout. Безліч View з Design Library вимагають CoordinatorLayout. Але чому? Сам по собі CoordinatorLayout робить не так багато, якщо використовувати його з View, що входять до складу Android фреймворку, то він буде працювати, як звичайний FrameLayout. То звідки береться його магія? Ось де на сцену виходить CoordinatorLayout.Behavior. Підключивши Behavior до дочірньої View у CoordinatorLayout, ви зможете перехоплювати торкання, віконні вставки (window insets), зміни розмірів та макета (measurement та layout), а також вкладену прокручування. Design Library широко використовує Behavior, щоб додати силу більшості функціоналу, яку ви бачите.

Створення Behavior
Створити Behavior досить просто: успадковуємо наш клас від Behavior:
Зверніть увагу на generic тип, вказаний у цьому класі. У цьому випадку ми вказуємо, що можемо підключити FancyBehavior до будь-якого View. Однак, якщо ви хочете дозволити підключати ваш Behavior до певного типу View, ви можете написати так:
Це може врятувати вас від приведення до потрібного вам підтипу View великої кількості параметрів у методах просто і зручно.
Є методи, щоб зберегти як тимчасові дані Behavior.setTag()/Behavior.getTag(), так і зберегти стан екземпляра Behavior за допомогою onSaveInstanceState()/onRestoreInstanceState(). Я закликаю вас створювати Behavior якомога легше, але ці методи дозволяють створювати Behavior з можливістю збереження стану.
Підключення Behavior
Звичайно, Behavior не робить нічого сам по собі, щоб ми могли ним користуватися, він повинен бути підключений до дочірньої View у CoordinatorLayout. Є три основніспособу зробити це: програмно, в XML або автоматично за допомогою інструкції.
Програмне підключення Behavior
Коли ви думаєте про Behavior як щось додатково підключене до кожної View всередині CoordinatorLayout, вас не повинно здивувати (якщо ви читали наш пост про макети) те, щоBehavior насправді зберігається в LayoutParams кожної View– ось чому Behavior має бути оголошений у View всередині CoordinatorLayout, тому що тільки у цих View є конкретний підтип LayoutParams, який може зберігати Behavior.
Як ви можете бачити, у цьому випадку ми використовуємо звичайний порожній конструктор. Але це не означає, що ви не можете створити конструктор, який приймає стільки параметрів, скільки захочете. Коли робите щось через код – немає межі можливостей.
Підключення Behavior через XML
Звичайно, якщо постійно робити все через код – це може спричинити безладдя. Як і у випадку з більшістю користувацьких LayoutParams, є відповідний layout_ атрибут, щоб зробити все те саме. У нашому випадку цеlayout_behaviorатрибут:
Тут, на відміну від програмного способу, завжди буде викликаний конструкторFancyBehavior(Context context, AttributeSet attrs). Зате, як бонус, ви можете оголосити будь-які інші атрибути користувача і витягти їх з XML AttributeSet, це важливо, якщо ви хочете (а ви захочете) дати можливість іншим розробникам налаштовувати функціонал вашого Behavior через XML.
Примітка: аналогічно до угоди найменування layout_ атрибутів, які батьківський клас повинен вміти аналізувати та розуміти, використовуйтепрефікс behavior_для будь-яких атрибутів, що використовуються всередині Behavior.
Автоматичне підключення Behavior
Якщо ви створюєте своюView, якій потрібен свій Behavior (як було у випадку з більшістю компонентів у Design Library), тоді ви, швидше за все, захочете підключити Behavior за умовчанням, без постійного ручного підключення через код або XML. Щоб зробити це, потрібно лише додати просту інструкцію зверху класу вашої View:
Ви помітите, що ваш Behavior буде викликаний за допомогою стандартного порожнього конструктора, роблячи цей спосіб схожим на програмне підключення Behavior. Зауважте, що будь-який атрибутlayout_behaviorперевизначить DefaultBehavior.
Перехоплення торкань
Як тільки ви підключите Behavior, ви готові до дій. Одна з можливостей Behavior – це перехоплення торкань.
БезCoordinatorLayoutце зазвичай залучало підкласи кожної ViewGroup, як сказано в Managing Touch Events training. Однак у випадку зCoordinatorLayout, він передасть виклики методу onInterceptTouchEvent() методу onInterceptTouchEvent() вашого Behavior,дозволяючи вашому Behavior перехоплювати торкання. Якщо повернути в цьому методіtrue, то ваш Behavior отримає всі наступні торкання за допомогою методу onTouchEvent() - все це відбувається таємно від View. Наприклад, так працює SwipeDismissBehavior з будь-яким View.
Є інший, складніший випадок перехоплення дотиків – блокування будь-якої взаємодії. Достатньо повернути у методі блоківInteractionBelow()true. Швидше за все ви захочете якось візуально показати, що взаємодія заблокована (щоб користувачі не подумали, що програма зламана) – ось чому стандартний функціоналblocksInteractionBelow()залежить від значення getScrimOpacity(). Повертаючи значення не рівне нулю, разом малюємо колір (getScrimColor(), чорний за замовчуванням) поверх View і відключаємо взаємодії торканням.Зручно.
Перехоплення віконних вставок
Скажімо, ви читали блог Why would I want to fitsSystemWindows?. Там ми докладно обговорювали, що робитьfitsSystemWindows, але все зводилося до представлення віконних вставок (window insets), необхідних щоб уникнути малювання під системними вікнами (такими як status bar та navigation bar). Behavior може проявити себе і тут. Якщо ваша ViewfitsSystemWindows=“true”, то будь-який підключений Behavior отримає виклик методу onApplyWindowInsets(), у пріоритеті над самим View.
Примітка: в більшості випадків, якщо ваш Behavior не обробляє віконні вставки, він повинен передати ці вставки за допомогою ViewCompat.dispatchApplyWindowInsets(), щоб переконатися, що всі дочірні View отримають можливість побачити WindowInsets.
Перехоплення Measurement і Layout
Це два ключові компоненти в тому, як Android малює View. Тому, має сенс, що Behavior, як перехоплювач всіх подій, так само першим отримає повідомлення про зміну розміру та макета шляхом виклику методів onMeasureChild() та onLayoutChild() .
Наприклад, давайте візьмемо будь-який generic ViewGroup і додамо до нього maxWidth. Як показано у класі MaxWidthBehavior.java.
Створення generic Behavior, який зможе працювати з будь-якими View, дуже зручне. Але пам'ятайте, що ви можете спростити собі життя, обдумавши чи буде Behavior використовуватися тільки всередині вашої програми (не всі Behavior поголовно повинні бути generic!).
Розуміння залежностей між View
Все описане вище вимагає єдиної View. Але справжня сила Behavior проявляється у побудові залежностей між View, тобто коли одна View змінюється, ваш Behavior отримує повідомлення про це і змінює свій функціонал залежно від зовнішніх умов.
Behavior може зробити View залежними один від одного двома різними шляхами: коли View пов'язана (anchor) з іншого View (мається на увазі залежність) або коли ви явно повертаєтеtrueу методі layoutDependsOn().
Зв'язка виникає, коли ваш View всерединіCoordinatorLayoutвикористовує атрибут layout_anchor. Цей атрибут, суміщений з layout_anchorGravity, дозволяє ефективно зв'язати положення двох View разом. Наприклад, ви можете зв'язатиFloatingActionButtonзAppBarLayout, тодіFloatingActionButton.Behaviorбуде використовувати неявну залежність, щоб сховати FAB, якщоAppBarLayout<11 прокручено за кордон екрана.
У будь-якому випадку, вашBehaviorотримає виклики методу onDependentViewRemoved(), коли залежна View була видалена і onDependentViewChanged(), щоразу як залежна View була змінена (змінився розмір або позиція).
Більшість класного функціоналу Design Library працює завдяки можливості зв'язування View разом. Візьміть, наприклад, взаємодію міжFloatingActionButtonіSnackbar.Behaviorу FAB залежить від доданих уCoordinatorLayoutекземплярівSnackbar, потім, використовуючи виклик методуonDependentViewChanged(), переміщає FAB вище, щоб не допустити перекриттяSnackbar.
Примітка: коли ви додаєте залежність, View завжди буде розміщена після залежної View незалежно від послідовності в розмітці.
Вкладена прокручування
О, вкладена прокручування. Я лише злегка торкнуся цієї теми у пості. Потрібно мати на увазі кілька речей:
- Ви не повинні оголошувати залежності від View c вкладеною прокручуванням. Кожна View всерединіCoordinatorLayoutможе отриматиподії вкладеної прокручування.
- Вкладена прокручування може виникнути не тільки у View всерединіCoordinatorLayout, але й убудь-якоїдочірньої View (наприклад, “дитина” “дитина” “дитина” уCoordinatorLayout) .
- Я називатиму це вкладеною прокруткою, але насправді під ним розумітиметься і просто прокрутка і швидка прокрутка (fling).
Після того як ви повернетеtrueу методіonStartNestedScroll(), вкладена прокрутка працює у два етапи:
- onNestedPreScroll() викликаєтьсяпередтим як прокручується View отримала подію прокручування і дозволяє вашомуBehaviorобробити частину або всю прокручування (останній int[] параметр – це “вихідний” параметр, де ви можете вказати яку частину прокручування обробив Behavior).
- onNestedScroll() викликається як тільки прокручується View була прокручена. Ви отримаєте значення наскільки View була прокручена та необроблені значення.
Коли вкладена (або швидка) прокручування закінчуються, ви отримаєте метод onStopNestedScroll(). Це означає закінчення прокручування та очікування нового виклику методуonStartNestedScroll()перед тим, як почнеться нова прокручування.
Візьміть, наприклад, випадок, коли ви хочете сховатиFloatingActionButton, коли прокручуємо вниз і показати FAB,коли прокручуємо вгору. Для цього потрібно перевизначити методиonStartNestedScroll()іonNestedScroll(), як видно в ScrollAwareFABBehavior.
І це лише початок
Якщо кожна окрема частина у Behavior лише цікава, то коли вони всі разом – починається магія. Я дуже рекомендую переглянути вихідний код Design Library, щоб дізнатися більше можливостей. Розширення для ChromeAndroid SDK Searchвсе ще один з моїх улюблених ресурсів для дослідження коду AOSP (Android Open Source Project). Крім цього ви можете знайти останні версії вихідного коду на шляху/extras/android/m2repository.
Дайте мені знати, як ви використовуєте отримані основи про те, що Behavior може хештегом #BuildBetterApps.
Приєднуйтесь до обговорення на Google+ та підписуйтесь на Android Development Patterns Collection для ще більшої інформації!
Хардкорна конфа за С++. Ми запрошуємо лише профі.