У недавньому оновленні support бібліотек Android з'явився новий компонент RecyclerView, який прийшов на заміну ListView і приніс багато нових можливостей. Він призначений для створення великих комплексних списків і дозволяє використовувати різні LayoutManagers. в тому числі і кастомниє.
На одному з проектів нам потрібно було створити нетрадиційне подання списку. Так ми написали свої лейаути Carousel LayoutManager і Expand LayoutManager. Їх докладний опис можна подивитися на GitHub.
У цій статті ми хочемо розповісти про основні принципи створення кастомних LayoutManagers. Реалізація власних лейаутов вимагає написання великої кількості коду. Щоб полегшити завдання, ми поділимося ключовими моментами.
Пишемо свій LayoutManager
До стандартних LayoutManagers відносяться:
- LinearLayoutManger - для класичних списків
- GridLayoutManager - для табличного виду
- StaggeredGridLayoutManager - для композиції елементів в стилі Pinterest
Навіть при такому виборі в окремих випадках типових лейаутов недостатньо для вирішення завдань по дизайну мобільних додатків.
Перш ніж створювати власний LayoutManager, переконайтеся, що ви знаєте за якими принципами його створювати.
Загальні правила створення кастомних лейаутов:
1. Малювати тільки те, що відображається на екрані.
2. Використовувати швидкий алгоритм для обчислення позицій елементів, а не пробігати по всьому списку з Adapter (RecyclerView.Adapter).
3. Зайвий раз не інфлейтіть views, які вже є на екрані, а перевикористати їх.
4. Не викликати requestLayout при кожному зручному випадку. Замість цього самим рухати в'юшки при необхідності. Наприклад, коли треба зробити анімацію.
Коли ви засвоїли основні правила створення власних лейаутов, переходите до методів.
Методи для реалізації лейаутов:
- onLayoutChildren - найголовніший метод, де відбувається будівництво елементів для відображення на екрані
- onMeasure - дуже важливий метод. Якщо розміри дочірніх views в LayoutManager хоч якось залежать від цього параметра, то важливо його обнулити, перерахувати його розміри і заново перерахувати всі дочірні views з потрібними розмірами.
- scrollToPosition (int) - дозволяє скролл весь лейаут до певної позиції в Adapter
- smoothScrollToPosition (recyclerView, state, position) - анімований скролл, який вимагає вказати напрямок прокрутки списку і тип швидкості анімації
- canScrollVertically / canScrollHorizontal - відмінний спосіб блокування можливості скролла в окремих напрямках
- scrollVerticallyBy / scrollHorizontalBy - дозволяє змінити стан LayoutManager. Використовуючи цей метод, необхідно проскролліть всі його елементи і повернути число, що дорівнює кількості зроблених скролів. Наприклад, можна повернути 0, тоді це буде майже рівнозначно забороні скороллінга в методах вище.
- onSaveInstanceState / onRestoreInstanceState - допомагає зберігати стан кастомними LayoutManager, наприклад, необхідно при переворотах
По суті, перелічені методи відповідають за функції, які виконують лейаути. Наприклад, наш ExpandLayoutManager дозволяє відображати додаткову інформацію по кожному елементу списку. Використовує різноманітні scrollBy, scrollHorizontallyBy для можливості скролінгу:
Наш Carousel LayoutManager знову ж задіє ScrollVerticallyBy і ScrollHorizontallyBy, щоб прокручувати список розділів програми. Він може працювати циклічно, тобто гортати список безкінечно, а може і до певного порогу. Крім цього, він підтримує scrollToPosition і smoothScrollToPosition для можливості негайного переходу на потрібний елемент з коду:
Ефект обертається каруселі та зуму лейауту надають архітектурні фішки у вигляді CarouselZoomPostLayoutListener. Зокрема, цей лістенер зменшує і трохи зміщує кожен елемент на екрані залежно від його положення по деякому математичного алгоритму.
Визначившись з методами і ключовими принципами написання лейаутов, можна переходити до архітектури.
Вивчаємо підхід до архітектури LayoutManagers
1. Організуйте стан LayoutManager так, щоб в будь-який момент часу знати його поточне зміщення, розташування першого і останнього елемента, їх розміри та інші важливі характеристики лейаута.
Наприклад, для ExpandLayoutManager це mScrollOffset, mDecoratredChildHeight, mExpandItemPosition, mExecutingAnimationData. За допомогою цих даних ми завжди можемо відстежити, в якому стані знаходиться наш LayoutManager. Це потрібно для того, щоб зуміти його відновити при onLayoutChildren, scrollVerticallyBy / scrollHorizontalBy і для підтримки анімації.
2. Видаліть непотрібні в'юшки перед заповненням потрібними. Це не означає, що треба видаляти всі views. Мається на увазі видалення тільки тих, які тепер стали невидимі.
3. Не створюйте нових views, якщо вони вже додані в LayoutManager. Пройдіться по всіх його в'юшки і знайдіть ту, яка зараз необхідна. Якщо така в'юшки була знайдена, то створіть її за допомогою getViewForPosition, а потім знову прив'яжіть її до потрібної позиції в лейауте за допомогою bindViewToPosition.
Є така особливість, що контент в Adapter може бути змінений. Потрібно вчасно ловити подібні ситуації, і коли елемент вже знайдений в списку дочірніх в'юшок, заново викликати на ньому bindViewToPosition. В іншому випадку можна втратити зміни адаптера.
4. Ще один метод state.didStructureChange викликається, коли ви зрушили, видалили або додали елементи з середини списку. Він показує зміни, що відбуваються в лейауте.
В цілому реалізація лейаута спрямована на додавання, вимір і розташування дочірніх views в заданому порядку в режимі реального часу. Тобто якщо користувач прокручує вікні керування зі списком розділів, саме від LayoutManager залежить, коли можуть бути додані нові дочірні в'юшки, а коли ховаються і видаляються старі. Тому якщо ваш лейаут має надійну архітектуру, то ви гарантуєте, що користувач буде без проблем взаємодіяти з додатком і елементами кастомними списку.