Классический диалог выбора даты для Android 7.0

tl;dr: ссылка на гитхаб

Эпизод 1: Отображение диалога

В мобильных приложениях иногда возникает надобность отображать диалог выбора даты. Например, на нашем последнем проекте, связанном с продажей алкогольной продукции, пользователь мог лицезреть вожделенную выпивку лишь после подтверждения того, что ему уже исполнился 21 год, таковы американские законы (кстати, из-за этого подтверждения мой 20-летний коллега работать над проектом теоретически не имел права и был вынужден коварно лгать приложению, которое сам и разрабатывал). Схема примерно такова:

Схема работы диалога выбора даты

  • Почему бы нам просто не сделать галочку с текстом «Мне уже исполнился 21 год»? — спросил я.
  • У всех наших конкурентов диалог выбора даты, значит и нам нужно делать так же — заказчик привел железный аргумент.

Показать диалог очень просто, в андроиде есть готовый класс для этого:

new DatePickerDialog(MainActivity.this,
            new DatePickerDialog.OnDateSetListener() {
    @Override
    public void onDateSet(DatePicker view,
            int year, int month, int dayOfMonth) {
        // let the user into the application
    }
}, year, month, day).show();
Эпизод 2. Календарики не нужны

И все было бы хорошо, да вот заказчик стал жаловаться на то, что на новых андроидах диалог какой-то странный и вообще не по дизайну:

Material Design date picker

Попытался объяснить, что на макетах был старый диалог, а это новый, выполненный в стиле «Material Design» и представленный в Android 5.0 (Lollipop), поэтому на новых андроидах следует использовать именно его. Но, как известно, клиент всегда прав, поэтому по итогам дискуссии я отправился делать старый диалог для всех версий андроида.

Увы, разработчики андроида не предусмотрели способа указать стиль диалога программно. Спасибо хотя бы на том, что старый стиль не был выпилен окончательно. Существует довольно простой способ привести все date picker’ы к старому стилю путем изменения атрибутов темы. Для этого в стилях устанавливаем нашей теме свойства dialogTheme и datePickerStyle:

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
  <item name="android:dialogTheme">@style/AppDialogTheme</item>
  <item name="android:datePickerStyle">@style/SpinnerDatePicker</item>
</style>

Теперь определим сами стили. В дефолтном файле styles.xml они будут пустыми, потому что на девайсах с осями до 5.0 все уже так, как нам надо:

<style name="AppDialogTheme"
    parent="Theme.AppCompat.Light.Dialog.Alert">
</style>
<style name="SpinnerDatePicker">
</style>

А вот начиная с 5.0 делаем следующее (путем помещения этих строк в values-v21/styles.xml):

<style name="AppDialogTheme"
    parent="Theme.AppCompat.Light.Dialog.Alert">
  <item name="android:datePickerStyle">
    @style/SpinnerDatePicker</item>
</style>
<style name="SpinnerDatePicker"
    parent="android:Widget.Material.DatePicker">
  <item name="android:datePickerMode">spinner</item>
</style>

У читателя может возникнуть вопрос — зачем мы определили свойство datePickerStyle одновременно в AppTheme и AppDialogTheme? Практика показала, что для одних девайсов требуется одно, для других другое, так что лучше подстраховаться и продублировать.

Эпизод 3: Нуга наносит ответный удар

Когда мы уже заканчивали работу над проектом, вышел Android 7.0 (Nougat). Изменений, затрагивающих основные фичи проекта, в нем не ожидалось, поэтому я со спокойным сердцем запустил приложение на девайсе с новым андроидом и действительно, не заметил каких-либо проблем. Кроме того, что диалог выбора даты внезапно снова стал модным и молодежным, в виде календарика. Ну да ладно, подумал я, невелика беда — кого в самом деле интересует стиль диалога? Дату выбрать можно, вот и хорошо. Главное тестерам не говорить. Однако, когда утром я пришел на работу, в джире меня уже ждал свежесозданный тикет по этому поводу.

Что ж, мне и самому стало интересно, в чем дело. Заглянув в документацию, я обнаружил там то, что и ожидал: пометку «This method was deprecated in API level 24. Not supported by Material-style calendar mode» у метода setSpinnersShown. Облегченно вздохнув, я отмахнулся от тикета, написав о том, что этот старый режим диалога выбора даты больше не поддерживается, а реализация этого режима с нуля займет неоправданно долгое время.

К моему удивлению, заказчик продолжил настаивать на том, что это очень важно, и нам действительно нужно написать собственную реализацию диалога, ибо, цитирую: «business really wants it seriously lots of money».

Поэкспериментировав, я обнаружил, что старый стиль на самом деле не выпилен из Android SDK, просто в разметке, используемой диалогом, свойство datePickerMode захардкожено в значении calendar, так что атрибуты темы оказываются бесполезными. Если же добавлять виджет DatePicker на форму, то ему по прежнему можно выставлять оба стиля и это будет работать на 7.0. Соответственно, можно относительно просто создать собственный диалог с собственной разметкой, который будет работать так, как требуется. Но можно поступить еще проще — взять готовую реализацию этого диалога из Android SDK версии и подправить ее так, чтобы атрибут темы брался во внимание. Сразу скажу, что перед этим я рассматривал альтернативные возможности, включая рефлексию, но увы, не придумал способа лучше. Не буду приводить весь код класса здесь, интересующиеся могут посмотреть на гитхабе. Фактически, единственное что я сделал — это стал использовать собственный layout вместо предоставляемого SDK. А сам layout отличается лишь тем, что в нем не захардкожен datePickerStyle.

Стоит также обратить внимание на то, что на осях < 7.0 в моей реализации используется стандартный DatePickerDialog из Android SDK, так как там он уже работает как надо, а взятая мной реализация содержит методы, недоступные на предыдущих версиях ОС.

Таким образом, было найдено временное быстрое решение до тех пор, пока в одной из версий андроида не выпилят старый DatePicker окончательно. Ну а когда это случится, как говорил Ходжа Насреддин, кто-то уже успеет умереть: либо султан, либо Ходжа, либо Android осёл.

Эпилог

Раздумывая о том, что у нас в проекте хватает и других, более серьезных проблем, над которыми стоило бы поработать, я решил отвлечься и зашел на страничку приложения в Google Play, почитать о том, чего действительно хотят пользователи (мы переписывали приложение с нуля на чистом андроиде, в то время как в магазине уже лежала версия, сделанная на Xamarin, в которой, кстати, диалог выбора даты превращался в календарик уже на Android 5.0 Lollipop).

Мое внимание привлекла оценка в одну звезду с комментарием занимательного содержания. На русском он начинается примерно так:

«Уважаемые разработчики! Для того, чтобы добраться до 1979 года, мне приходится прокручивать около 500 раз».

Потом я увидел и другие «однозвездочные» комментарии подобного содержания. Да что там говорить, примерно каждый 5-й комментатор жаловался на то, что приходится очень долго прокручивать «календарик» для того, чтобы добраться до своего года рождения.

И тут я понял, насколько был неправ, и насколько важен на самом деле этот злосчастный диалог для рейтинга и успешности приложения. И нет смысла винить тупых пользователей, виноват только Google со своим абсолютно неинтуитивным интерфейсом, в котором даже я, Android-разработчик, далеко не сразу нашел возможность переключиться с прокрутки месяцев на прокрутку лет. Если вы тоже не нашли, подсказываю: нужно нажать сюда:

Как перейти к выбору года

Не очень user-friendly, согласны? Вот и я так думаю.

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

Ваш адрес email не будет опубликован.