Все, что вам нужно знать, чтобы раскрыть потенциал Jetpack Compose Previews в качестве эксперта

Jetpack Compose упрощает создание экранов приложений для Android. Одна из его интересных особенностей — аннотация @Preview. Это позволяет нам быстро увидеть, как выглядят наши проекты, не запуская полную версию приложения. Но иногда нам нужно видеть наши проекты по-разному, например, в темном или светлом режиме. Повторение одних и тех же настроек для каждого компонента может привести к путанице. Вот тут-то и приходят на помощь пользовательские аннотации.

1. Контекстно-зависимый предварительный просмотр: светлая и темная тема.

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

@Preview(
    name = "Dark Mode",
    showBackground = true,
    uiMode = UI_MODE_NIGHT_YES
)
@Preview(
    name = "Light Mode",
    showBackground = true,
    uiMode = UI_MODE_NIGHT_NO
)
@Composable
fun PreviewListItem() {
    // Your Composable here
}

Это кусок кода, особенно если вам нужно реплицировать его на несколько компонентов. Давайте инкапсулируем это с помощью специальной аннотации:

@Preview(name = "Dark Mode", showBackground = true, uiMode = UI_MODE_NIGHT_YES)
@Preview(name = "Light Mode", showBackground = true, uiMode = UI_MODE_NIGHT_NO)
annotation class ThemePreviews

@ThemePreviews
@Composable
fun PreviewsListItem() {
    MaterialTheme {
        Surface {
            PortraitListItem(
                imageResource = painterResource(id = R.drawable.task),
                text = "Write the `Preview` Medium article",
                isChecked = false,
                onCheckedChange = { }
            )
        }
    }
}

Вы можете получить код для PortraitListItem здесь:
https://gist.github.com/mo0rti/260c35752a3718d54e774ab3ccbc0179

С @ThemePreviews ваш предварительный просмотр выглядит так:

2- Предварительный просмотр ориентации устройства: портретная и альбомная

Jetpack Compose обычно отображает предварительный просмотр в вертикальном портретном стиле. Но вы можете сделать свой компонент адаптивным как в альбомной, так и в портретной ориентации. Мы можем использовать контекст LocalConfiguration в функции @Composable и создать компонент адаптивной композиции.

@Composable
fun AdaptiveListItem(
    imageResource: Painter,
    text: String,
    isChecked: Boolean,
    onCheckedChange: (Boolean) -> Unit
) {
    val configuration = LocalConfiguration.current
    if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        LandscapeListItem(imageResource, text, isChecked, onCheckedChange)
    } else {
        PortraitListItem(imageResource, text, isChecked, onCheckedChange)
    }
}

На следующем шаге вы можете создать следующую пользовательскую аннотацию:

@Preview(name = "Landscape Mode", showBackground = true, device = Devices.AUTOMOTIVE_1024p, widthDp = 640)
@Preview(name = "Portrait Mode", showBackground = true, device = Devices.PIXEL_4)
annotation class OrientationPreviews

@OrientationPreviews
@Composable
fun OrientationPreviewsListItem() {
    MaterialTheme {
        Surface {
            AdaptiveListItem(
                imageResource = painterResource(id = R.drawable.task),
                text = "Write the `Preview` Medium article",
                isChecked = false,
                onCheckedChange = { }
            )
        }
    }
}

Я использовал Devices.PIXEL_4 для отображения в портретном режиме, и это мое личное предпочтение. Для ландшафтного режима я использую Devices.AUTOMOTIVE_1024p и ограниченную ширину 640 DP, чтобы мой предварительный просмотр можно было легко увидеть в инструментах предварительного просмотра Android Studio.

3- Предварительный просмотр масштабирования шрифта: различные настройки доступности.

@Preview(name = "Default Font Size", fontScale = 1f)
@Preview(name = "Large Font Size", fontScale = 1.5f)
annotation class FontScalePreviews

@FontScalePreviews
@Composable
fun FontScalePreviewsListItem() {
    // Your Composable here
}

Вы также можете комбинировать эту пользовательскую аннотацию с новыми параметрами предварительного просмотра:

@Preview(
    name = "Extra Large Font Size",
    fontScale = 2f
)
@FontScalePreviews
annotation class NewFontScalePreviews

@NewFontScalePreviews
@Composable
fun NewFontScalePreviewsListItem() {
    // Your Composable here
}

4- Предварительный просмотр направлений макета: поддержка LTR и RTL для международных приложений.

@Preview(name = "Left-To-Right", locale = "en")
@Preview(name = "Right-To-Left", locale = "ar")
annotation class LayoutDirectionPreviews

@LayoutDirectionPreviews
@Composable
fun LayoutDirectionPreviewsListItem() {
    // Your Composable here
}

5- Интерактивность с Preview

Интерактивный режим Jetpack Compose позволяет взаимодействовать с компонентами внутри предварительного просмотра. Это невероятно полезно для тестирования небольших взаимодействий без развертывания приложения:

@Composable
fun ListItemPreview() {
    var checkedState by remember { mutableStateOf(false) }

    PortraitListItem(
        imageResource = painterResource(id = R.drawable.task),
        text = "Write the `Preview` Medium article",
        isChecked = checkedState,
        onCheckedChange = { newState ->
            checkedState = newState
        }
    )
}

@Preview(showBackground = true, name = "Interactivity Preview")
@Composable
fun PreviewListItem() {
    ListItemPreview()
}

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

Перейдя в интерактивный режим, вы можете просмотреть свой компонент в разных состояниях.

Примечание. Имейте в виду, что по завершении тестирования всегда следует отключать интерактивный режим, иначе вы не увидите новые изменения в предварительном просмотре. Вы можете найти его в верхнем левом углу окна предварительного просмотра.

6- Разные превью с разными данными

Визуализация того, как ваши компоненты пользовательского интерфейса адаптируются к различным сценариям использования данных, имеет жизненно важное значение. С @PreviewParameter от Jetpack Compose эта задача становится проще простого. Давайте рассмотрим несколько практических примеров того, как можно максимально эффективно использовать эту функцию!

а. Пробуем текст разной длины

Вы когда-нибудь задумывались, как в вашем дизайне выглядит крошечная метка или абзац длиной в эссе? Давай выясним.

class TextPreviewProvider : PreviewParameterProvider<String> {
    override val values = sequenceOf(
        "Short Text",
        "A bit longer text.",
        "This one is really, really long. Like, really long!"
    )
}

@Composable
@Preview
fun DifferentTextPreviewsListItem(
    @PreviewParameter(TextPreviewProvider::class) text: String
) {
    MaterialTheme {
        Surface {
            PortraitListItem(
                imageResource = painterResource(id = R.drawable.task),
                text = text,
                isChecked = false,
                onCheckedChange = { }
            )
        }
    }
}

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

б. Предварительный просмотр различных изображений

Изображения бывают всех форм и размеров. Давайте посмотрим, как разные изображения вписываются в наш дизайн.

class ImagePreviewProvider : PreviewParameterProvider<Int> {
    override val values = sequenceOf(
        R.drawable.pic1,
        R.drawable.pic2,
        R.drawable.pic3
    )
}

в. Переключение тем

В приложениях, предлагающих светлый и темный режимы, вы можете увидеть оба режима одновременно.

class ThemePreviewProvider : PreviewParameterProvider<Colors> {
    override val values = sequenceOf(LightColorPalette, DarkColorPalette)
}

д. Различные состояния компонентов

От активного до загрузки, просмотрите каждое состояние, в котором может находиться ваш компонент.

enum class ButtonState {
    Active, Disabled, Loading
}

@Composable
fun MyComposeButton(
    text: String,
    buttonState: ButtonState,
    onClick: () -> Unit
) {
    // ... Your code for the button goes here...
}

// You can preview the button with different states
class ButtonStateProvider : PreviewParameterProvider<ButtonState> {
    override val values = sequenceOf(
        ButtonState.Active,
        ButtonState.Disabled,
        ButtonState.Loading
    )
}

@Composable
@Preview
fun MyButtonPreview(
    @PreviewParameter(ButtonStateProvider::class) buttonState: ButtonState
) {
    MaterialTheme {
        Surface {
            MyComposeButton(
                text = "My Button",
                buttonState = buttonState,
                onClick = { }
            )
        }
    }
}

е. Поддельные данные API

Хотя мы не можем получать данные в реальном времени в предварительном просмотре, вы можете имитировать данные из своего API. Таким образом, вы всегда готовы к тому, что бросает вам ваш API.

data class UserProfile(val name: String, val bio: String, val avatar: Int)

class UserProfileProvider : PreviewParameterProvider<UserProfile> {
    override val values = sequenceOf(
        UserProfile("Alice", "Loves hiking and coffee", R.drawable.avatar1),
        UserProfile("Bob", "Avid reader and tech enthusiast", R.drawable.avatar2),
        UserProfile("Charlie", "Just here for the memes", R.drawable.avatar3)
    )
}

@Composable
fun UserProfileComposable(profile: UserProfile) {
    // Your compose code is here
}

@Composable
@Preview
fun UserProfilePreview(
    @PreviewParameter(UserProfileProvider::class) userProfile: UserProfile
) {
    MaterialTheme {
        Surface {
            UserProfileComposable(profile = userProfile)
        }
    }
}

И ваш предварительный просмотр выглядит так:

Вы можете ограничить объем данных, которые хотите отображать в предварительном просмотре, с помощью параметра limit:

@Composable
@Preview
fun LimitedUserProfilePreview(
    @PreviewParameter(UserProfileProvider::class, limit = 2) userProfile: UserProfile
) {
    MaterialTheme {
        Surface {
            UserProfileComposable(profile = userProfile)
        }
    }
}

ф. Направление макета:

При создании компонентов пользовательского интерфейса важно визуализировать, как они будут выглядеть в режимах Ltr и Rtl.

object DirectionPreviews : PreviewParameterProvider<LayoutDirection> {
    override val values: Sequence<LayoutDirection> = sequenceOf(
        LayoutDirection.Ltr,
        LayoutDirection.Rtl
    )
}

@Preview
@Composable
fun PreviewWithDirection(
    @PreviewParameter(DirectionPreviews::class) direction: LayoutDirection
) {
    CompositionLocalProvider(LocalLayoutDirection provides direction) {
        MaterialTheme {
            Surface {
                PortraitListItem(
                    imageResource = painterResource(id = R.drawable.task),
                    text = "Write the `Preview` Medium article",
                    isChecked = false,
                    onCheckedChange = { }
                )
            }
        }
    }
}

Последний вариант использования: давайте объединим все вместе

Предположим, вы создаете приложение, которое должно поддерживать несколько ориентаций экрана, и вам нужно иметь предварительный просмотр с динамическим списком данных для имитации реального использования. Этого можно добиться с помощью комбинации OrientationPreviews и PreviewParameter.

@Composable
@OrientationPreviews
fun DifferentTextPreviewsListItem(
    @PreviewParameter(TextPreviewProvider::class) text: String
) {
    MaterialTheme {
        Surface {
            PortraitListItem(
                imageResource = painterResource(id = R.drawable.baseline_assignment_turned_in_24),
                text = text,
                isChecked = false,
                onCheckedChange = { }
            )
        }
    }
}

Ваш предварительный просмотр выглядит так:

Вы можете создавать более сложные превью, комбинируя различные аннотации предварительного просмотра:

@Composable
@OrientationPreviews
@LayoutDirectionPreviews
fun DifferentTextPreviewsListItem(
    @PreviewParameter(TextPreviewProvider::class) text: String
) {
    // Your preview code here
}

Могу поспорить, что вы уже можете догадаться о результате 😄

Это обертка! Благодаря Jetpack Compose Preview и @PreviewParameter вы можете создавать идеальные по пикселям проекты независимо от данных.

Понравилась эта статья? Не забудьте поделиться!

Приятного сочинения!