Jetpack Compose изменил разработку пользовательского интерфейса благодаря декларативному подходу, упростив написание кода. Одна из его отличительных особенностей — предварительные просмотры Compose, которая позволяет разработчикам мгновенно увидеть, как Composable функции будут выглядеть на устройстве, без необходимости запускать приложение на устройстве или эмуляторе.
Это означает, что они демонстрируют не только статичный макет, но и состояние UI, полученное на основе логики пользовательского интерфейса в Composable, и даже могут воспроизводить анимацию, если она включена, среди прочих возможностей.
В отличие от этого, старые представления Android на основе XML позволяют предварительно просматривать макеты. Но они ограничиваются статичными макетами с содержимым, не представляющим никакой реальной логики или данных — они настолько просты, насколько это возможно.
Так что если некоторые части вашего приложения все еще полагаются на представления на основе XML, вы можете упустить всю мощь предварительных просмотров Compose… или нет?
Ведь Compose Preview действительно можно использовать с XML-представлениями!
Почему предварительный просмотр XML-макетов не работает
Представим, что у нас есть следующий RecyclerView ViewHolder
и соответствующий ему XML-макет Preview
.
Разница между ними очень заметна. Это связано с тем, что ViewHolder
включает UI-логику, которая не может быть представлена в статическом XML-представлении. Например:
- Динамическая инъекция представлений:
ViewHolder
программно внедряет дополнительные представления, такие как радиокнопки (т.е. флаги языков). - Использование кастомных представлений: В состав входит пользовательское представление для отображения количества «слов для обучения», состоящее из двух TextView, видимость которых меняется во время выполнения, чтобы анимировать подсчет и уменьшение чисел. Хотя можно использовать ссылку на атрибут tools, т. е.
tools:
для предварительного просмотра данных, это не может имитировать поведение этого пользовательского представления во время выполнения.
При таком динамическом поведении предварительный просмотр XML-макета становится практически бесполезным.
Включение предварительного просмотра Compose для ваших XML-макетов
Рендеринг представления состоит из двух основных частей:
- Inflating: Представление, созданное путем «раздувания» связанного XML-макета. Для ViewHolder этот View передается в качестве аргумента в конструктор.
- Binding: Метод, передающий состояние пользовательского интерфейса для рендеринга в View или ViewHolder.
// 1. Inflating val container = LayoutInflater .from(context) .inflate(R.layout.mylayout, parentViewGroup) // Create the ViewHolder with the container val viewHolder = MyViewHolder( container = container, // other arguments, e.g. listeners, here... ) // 2. Binding viewHolder.bind(uiState)
Когда дело доходит до предварительного просмотра ViewHolder в Compose Preview, нам сначала нужно получить доступ к его базовому представлению. Мы можем получить доступ к нему через свойство itemView
ViewHolder.
Но как отобразить это представление в предварительном просмотре Compose? Решение кроется в композабл AndroidView
. Если вы интегрировали Compose в существующее приложение на основе XML, вы, возможно, уже использовали AndroidView
для обертывания существующих экземпляров пользовательских представлений и их интеграции в иерархию Compose.
Именно этот трюк мы будем использовать для отображения ViewHolder — и любого другого представления на основе XML — внутри Compose Preview.
Давайте применим это на примере предыдущего ViewHolder!
@Preview @Composable fun TrainingViewHolderPreview() { AndroidView( // You might prefer fillMaxheight or fillMaxSize // depending on the container's layout constraints modifier = Modifier.fillMaxWidth(), factory = { context -> // 1. inflating val containerParent = FrameLayout(contextWrapper) val container = LayoutInflater .from(context) .inflate(R.layout.training_row, containerParent) val viewHolder = TrainingViewHolder(container = layout) // 2. Binding viewHolder.bind( item = initialTrainingItem, languageClickedListener = null // or a meaningful listener ) .itemView }, ) }
А вот как выглядит предварительный просмотр по сравнению с тем, что мы видим на устройстве:
Как мы видим, предварительный просмотр не полностью соответствует тому, что мы видим на устройстве. Это связано с тем, что реализация нашего viewHolder.bind()
запускает некоторые анимации, и, похоже, предварительный просмотр отражает их состояние до того, как они будут полностью выполнены.
Вот тут-то и пригодится интерактивный режим предварительного просмотра (Preview Interactive Mode).
В большинстве случаев Compose Preview отображаются очень точно, в том числе и при использовании представлений на основе XML. Это исключительный случай, связанный с анимацией, запускаемой при создании ViewHolder.
Интерактивный режим предварительного просмотра
Запуск режима Preview Interaction позволяет нам быстро пронаблюдать полную анимацию, выполняемую в viewHolder.bind()
:
Тем не менее, он все еще не идентичен тому, что мы видим на реальном устройстве: британский флаг должен быть отключен, чтобы соответствовать «6 словам для обучения» (5 русских, 1 немецкое). Это связано с тем, что он запускается в Android Studio, которая использует Layoutlib для рендеринга Composable.
Layoutlib — это кастомная версия фреймворка Android, специально разработанная для работы вне Android-устройств. Его цель — обеспечить предварительный просмотр макета в Android Studio, который очень похож на рендеринг на устройстве, хотя это может быть и не точное совпадение.
При скриншот тестах этого превью с помощью различных библиотек, что можно сделать с помощью ComposablePreviewScanner, результаты получаются разными:
- Paparazzi генерирует скриншот, отражающий то, что мы видим в интерактивном режиме Preview, что неточно.
- Инструмент тестирования скриншотов Compose Preview Screenshot Test сбоит при раздувании ViewHolder, о чем я сообщал в этом issue. Однако ожидается, что в обычных условиях он будет отображать те же результаты, которые мы видим в Preview. Это связано с тем, что он также основан на Layoutlib, как и Paparazzi.
- Roborazzi, который основан на JVM, а также все библиотеки скриншот-тестирования на основе инструментария, такие как Dropshots, Shot или Android-Testify, генерируют точные скриншоты, идентичные тому, что отображается на устройстве. Ни одна из них не использует Layoutlib.
Вы можете проверить это самостоятельно в репозитории Github Android Screenshot Testing Playground в модуле :recyclerviewscreen-previews
.
Таким образом, похоже, что это ошибка, связанная с Layoutlib.
Поскольку все библиотеки, основанные на сторонних инструментах, отображают экран правильно, мы можем предположить, что если запустить превью на устройстве с отключенной анимацией, результат будет точным.
И тут на помощь приходит опция Run Preview.
Run Preview
Эта опция запускает предварительный просмотр на эмуляторе или физическом устройстве, и это очень быстро, если эмулятор уже запущен.
Поскольку превью запускается на эмуляторе или устройстве, для рендеринга превью используется настоящий Android Framework, а не Layoutlib. Как следствие, это наиболее точный вариант проверки того, как отображается код в ваших превью.
При запуске предварительного просмотра на устройстве британский флаг, как и ожидалось, отображается отключенным.
Однако у этого способа есть недостаток: он отображает конфигурацию (т. е. локаль, режим пользовательского интерфейса, размер шрифта и т.д.) устройства, а не Preview.
Однако это все равно эффективнее, чем устанавливать приложение и переходить к экрану, на котором находится компонент для предварительного просмотра.
Заключение
Мы рассмотрели, как Compose Preview могут рендерить макеты на основе XML, продемонстрировав несколько преимуществ по сравнению с традиционными XML-превью:
- Проверка связанной логики пользовательского интерфейса представления
- Предварительный просмотр анимации в интерактивном режиме
- Быстрый запуск предварительного просмотра на устройстве, чтобы убедиться в его точности.
Мы также рассмотрели, как эффективно использовать интерактивный режим Compose Preview и опции Run Preview, их недостатки, а также то, как рендеринг в Preview соотносится с файлами скриншотов, генерируемыми наиболее популярными библиотеками скриншот-тестирования, в зависимости от того, основаны они на Layoutlib или нет.
Использование Compose Preview с представлениями на основе XML открывает новые возможности. Благодаря таким библиотекам, как ComposablePreviewScanner, мы можем автоматически генерировать скриншот-тесты из Preview и запускать их с помощью выбранной нами библиотеки тестирования скриншотов. Это позволяет нам:
- Плавно перенести наш пользовательский интерфейс с макетов на основе XML на Jetpack Compose
- Привнести композитную разработку, основанную на предварительном просмотре, в представления на основе XML.
Эти инструменты не только экономят время, но и помогают нам внедрить лучшие практики разработки UI.
Примеры кода из этой статьи вы можете найти в репозитории Android Screenshot Testing Playground.