DrawerLayout и ActionBarSherlock: скрестить ужа и ежа

Многие знают, что у проекта ActionBarSherlock некоторое время назад появился официальный конкурент от Google: 18-я версия Android Support Library среди прочих плюшек принесла и долгожданный ActionBar в режиме совместимости.

Похоже, под закат линейки 2.x Корпорация Добра всё-таки признала необходимость хоть какого-то единообразия внешнего вида приложений; раньше официальная позиция фактически сводилась к требованию писать две программы, с action bar-ом и без него. Естественно, Шерлок на фоне такого маразма быстро взлетел в топы используемых библиотек, а его автор, — Jake Wharton, — стал широко известным в узких кругах человеком. Но есть ли смысл отказываться от ActionBarSherlock сейчас?

Нет. Нет смысла.

ActionBarSherlock внутри Google Hangouts!
ActionBarSherlock внутри Google Hangouts!

Во-первых, Шерлок уже использован в тысячах проектов. Более того, он использовался даже Google, как минимум в приложении Hangouts. А переписывать кучу кода, предназначенного лишь для совместимости со старыми ОС — не лучшая идея. Во-вторых — Sherlock, за время своего становления фактическим стандартом, в свою очередь оброс вспомогательными библиотеками, и не факт что все они получат google-совместимую версию. Наконец, и качество Шерлока, похоже, пока чуть повыше качества гугловского решения. В общем, версию из support library лучше оставить для новых или некрупных проектов.

Есть, впрочем, и неприятные детали. Вместе с compat-версией ActionBar, Гугл любезно дал нам и другой контрол — DrawerLayout. То самое выезжающее сбоку меню, о котором подробнее рассказано здесь: DrawerLayout: панелька из гугла. И если вы пробовали использовать этот drawer вместе с Шерлоком, то с большой долей вероятности разочаровались в результате. Но можно не спешить с выводами — существует и прекрасно работающий способ скрестить эти интерфейсные решения малой кровью!

Проблемное место

Я уже упоминал, что DrawerLayout реализован в очень «ненавязчивой» манере; похвалы качеству ActionBarSherlock и вовсе отзвучали парой абзацев выше. Потому, собственно, все проблемы их взаимодействия сводятся лишь к одной нестыковке. ActionBarDrawerToggle, отвечающий за корректную визуальную интеграцию drawer-а в приложение, требует вызова onOptionsItemSelected() с передачей экземпляра android.view.MenuItem; Шерлок же использует свою версию MenuItem, а в SherlockActivity «родные» методы Android объявлены как final. Всё. На практике это единственное препятствие!

Первый же напрашивающийся путь решения проблемы — небольшое переписывание SherlockActivity или SherlockFragmentActivity, т.е. собственно удаление мешающих нам final-ов. После этого можно просто работать с DrawerLayout согласно официальному мануалу (не забыв, разумеется, вызвать super-метод).

Этот подход вполне жизнеспособен, однако не лишён очевидных минусов. Во-первых, стороннюю библиотеку правильнее всего использовать как «чёрный ящик», не изменяя её кода. Это позволит без всяких проблем обновлять её в случае выхода новых версий, и при этом не включать сторонний код в репозиторий проекта. Во-вторых — два почти одинаковых метода в одном классе будут смотреться не очень красиво и вызывать некоторую путаницу. Потому более правильной представляется другая методика.

Матрёшка

Идея этого метода проста как мычание.

ActionBarSherlock заворачивает android.view.MenuItem в свою реализацию? Ну и пусть — мы перезавернём его реализацию обратно в «родной» MenuItem!

Russian Doll Matryoshka
Russian Doll Matryoshka

Более того, нам фактически потребуется только getItemId(), но раз уж android.view.MenuItem является интерфейсом, то реализовать придётся все его методы. Можно оставить их сгенерированными по умолчанию (возвращающими null, false и т.п.), но я предпочёл «пробросить» все возможные вызовы: работы на минуту, а вдруг Google что-то поменяет в будущем? Запас кармана не оттянет.

Класс назовём RewrappedMenuItem, и реализация его абсолютно тривиальна — готовую версию можно найти в демо-проекте, или скачать напрямую:

RewrappedMenuItem.java

А использование получается ещё проще:

И после этой маленькой хитрости ActionBarDrawerToggle начинает отлично кушать нашу «матрёшечную» версию, отображая в точности тот же самый drawer, который мы привыкли видеть в приложениях Google.

Осталась лишь небольшая тонкость.

Проблемы Android 2.х

Думаете, могло обойтись без этого раздела? Увы нам! Не обошлось. На старых андроидах проблемы таки возникают, однако носят настолько косметический характер, что можно их даже не заметить.

Заключается проблема в том, что в имитации action bar-а не появляется индикатор наличия drawer-а, хотя всё остальное работает. Отображается просто стандартная стрелочка «назад», ведь ActionBarDrawerToggle ничего не знает об устройстве шерлока, и это у них взаимно.

Самое простое решение напрашивается сразу же — напрямую указать нужный индикатор для action bar, т.е. прописать его в тему для Activity:

Какие у этого решения недостатки? Очевидно — статичность картинки: на старых телефонах не будет красивой анимации «лёгкого сдвига индикатора». Возможно, в этом нет ничего страшного: пользователи старых аппаратов и так не избалованы лишними рюшечками, кроме того — относятся к медленно вымирающему виду. Потому, если устройства 2.x не критичны, то можно всё оставить так. Но если анимация на старых аппаратах всё-таки нужна, то метод есть!

Добавление анимации индикатора на Android 2.x

Анимацию, в общем-то, добавить несложно. Минусы здесь лишь в одном: хотя нам и не потребуется менять код самой библиотеки ActionBarSherlock, мы всё же немного «завяжемся» на её внутреннее устройство, что идеологически не вполне правильно. Нехорошо лишний раз лезть чёрному ящику под крышку, но раз надо — значит, надо.

Фрагмент экрана Android 2.3, видна анимация индикатора
Фрагмент экрана Android 2.3, видна анимация индикатора

При решении этой задачки следует ещё раз воздать хвалу продуманности класса ActionBarDrawerToggle. Всё обращение к UI в нём идёт через делегирующий объект, и нам предоставлена возможность этот объект заменять, просто реализовав интерфейсы Delegate и DelegateProvider. Схема работы выглядит примерно так:

Схема работы ActionBarDrawerToggle
Логика выбора делегатов ActionBarDrawerToggle. Тупиковых путей нет!

При всей скудности документации по делегатам, в ней сразу бросается в глаза метод setActionBarUpIndicator(), который нам как раз и нужен. Там всё просто до безобразия — на входе мы получаем уже полностью готовый к отрисовке Drawable индикатора, плюс некий content description, который на старых системах можно игнорировать. Не нужен нам, соответственно, и метод setActionBarDescription(), ну а getThemeUpIndicator() должен просто вернуть значок индикатора по умолчанию. Кроме того, нужно не забыть пометить нашу активити как DelegateProvider-а. Здесь всё очень просто.

Сложности начинаются со стороны Шерлока. У него нет (и, наверное, не будет) никаких API для изменения иконки «назад». Стало быть, ImageView этой иконки потребуется найти вручную, и здесь поджидает вторая засада: у неё нет уникального ID, вернее — он немножечко не совсем уникален. Потому придётся слегка поплясать по дереву вьюшек.

Не буду тянуть: по итогам всех изысканий получается примерно такое — комментарии к коду, как всегда, скажут больше любого подробного описания:

Код содержит изрядную порцию залезания под капот, и работает как минимум с последней версией ActionBarSherlock на момент написания текста. С более старой версией (или более новой, хотя выход её сомнителен) трюк именно в таком виде может и не сработать, но общий принцип останется неизменным.

Нужно ли уточнять, что здесь кроется механизм встраивания индикатора вообще куда угодно? Скажем, action bar в приложении может в принципе отсутствовать, но индикатор добавить это не помешает.

That’s all Folks!

Уникален ли предложенный здесь метод? Никак нет — не уникален, не защищён патентами, даже и не претендует. Было бы странно, если столь примитивные вещи придумал я один. «Матрёшкой» Шерлока с DrawerLayout-ом уже скрещивали, а полученный гибрид даже гордо зовётся некой библиотекой и достаточно популярен. Просто дам ссылку на гитхаб с примером кода, и буду считать её подтверждением правильности методики:

https://github.com/tobykurien/SherlockNavigationDrawer.

Анимации индикатора там, правда, нет — видимо, автор кода тоже придерживается мнения, что старые телефоны вполне перебьются, а ужа и ежа всё-таки скрещивают до определённых рамок приличия.

Удачной всем селекционной работы!

И напоминаю, что демо-проект скачивается тут: DemoDrawer.zip

DrawerLayout и ActionBarSherlock: скрестить ужа и ежа
2.6, голосов: 5


Комментарии:

2 комментария на «“DrawerLayout и ActionBarSherlock: скрестить ужа и ежа”»

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

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

Капча (решите пример) *