Поддержка различных размеров дисплея, поддержка различных размеров дисплея

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

Чтобы поддерживать как можно больше размеров дисплеев — будь то разные экраны устройств или разные окна приложений в многооконном режиме — создавайте макеты приложений так, чтобы они были отзывчивыми и адаптивными. Адаптивные/адаптивные макеты обеспечивают оптимизированное взаимодействие с пользователем независимо от размера дисплея, позволяя вашему приложению работать с телефонами, планшетами, складными устройствами, устройствами ChromeOS, книжной и альбомной ориентацией, а также с изменяемыми конфигурациями дисплея, такими как режим разделенного экрана и оконное оформление рабочего стола.

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

Jetpack Compose как декларативный набор инструментов пользовательского интерфейса идеально подходит для проектирования и реализации макетов, которые динамически изменяются для различного отображения контента на дисплеях разных размеров.

Вносите явные большие изменения в макет для составных элементов на уровне контента.

Компонуемые объекты на уровне приложения и контента занимают все пространство дисплея, доступное вашему приложению. Для этих типов компонуемых объектов может иметь смысл изменить общий макет вашего приложения на больших дисплеях.

Избегайте использования значений физического оборудования для принятия решений по компоновке. Может возникнуть соблазн принять решения, основанные на фиксированной материальной ценности (является ли устройство планшетом? Имеет ли физический экран определенное соотношение сторон?), но ответы на эти вопросы могут оказаться бесполезными для определения пространства, доступного для вашего пользовательского интерфейса. .

Рисунок 1. Форм-факторы телефона, складного устройства, планшета и ноутбука

На планшетах приложение может работать в многооконном режиме, то есть оно может разделять экран с другим приложением. В оконном режиме рабочего стола или в ChromeOS приложение может находиться в окне изменяемого размера. Физических экранов может быть даже несколько, например, в складном устройстве. Во всех этих случаях физический размер экрана не имеет значения для принятия решения о том, как отображать контент.

Вместо этого вам следует принимать решения на основе фактической части экрана, выделенной вашему приложению, описываемой текущими метриками окна, предоставляемыми библиотекой Jetpack WindowManager . Пример использования WindowManager в приложении Compose см. в образце JetNews .

Адаптация макетов к доступному пространству дисплея также уменьшает объем специальной обработки, необходимой для поддержки таких платформ, как ChromeOS, и форм-факторов, таких как планшеты и складные устройства.

Определив метрики пространства, доступного для вашего приложения, преобразуйте необработанный размер в класс размера окна, как описано в разделе Использование классов размера окна . Классы размеров окон — это точки останова, предназначенные для того, чтобы сбалансировать простоту логики приложения с гибкостью оптимизации вашего приложения для большинства размеров дисплея. Классы размера окна относятся к общему окну вашего приложения, поэтому используйте классы для решений по макету, которые влияют на общий макет вашего приложения. Вы можете передать классы размера окна как состояние или выполнить дополнительную логику для создания производного состояния для передачи вложенным составным объектам.

@Composable
fun MyApp(
    windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
) {
    // Perform logic on the size class to decide whether to show the top app bar.
    val showTopAppBar = windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT

    // MyScreen knows nothing about window sizes, and performs logic based on a Boolean flag.
    MyScreen(
        showTopAppBar = showTopAppBar,
        /* ... */
    )
}

Многоуровневый подход ограничивает логику размера дисплея одним местом вместо того, чтобы разбрасывать ее по всему приложению во многих местах, которые необходимо синхронизировать. Одно местоположение создает состояние, которое может быть явно передано другим составным объектам, как и любое другое состояние приложения. Явная передача состояния упрощает отдельные составные объекты, поскольку они принимают класс размера окна или указанную конфигурацию вместе с другими данными.

Гибкие вложенные составные элементы можно использовать повторно.

Составные элементы более пригодны для повторного использования, если их можно разместить в самых разных местах. Если составной объект необходимо разместить в определенном месте с определенным размером, маловероятно, что составной объект можно будет повторно использовать в других контекстах. Это также означает, что отдельные, многократно используемые составные элементы должны избегать неявной зависимости от глобальной информации о размере дисплея.

Представьте себе вложенный составной объект, реализующий макет списка , который может отображать либо одну панель, либо две панели рядом:

Приложение, показывающее две панели рядом.
Рис. 2. Приложение, демонстрирующее типичный макет списка: 1 — область списка; 2 , область детализации.

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

@Composable
fun AdaptivePane(
    showOnePane: Boolean,
    /* ... */
) {
    if (showOnePane) {
        OnePane(/* ... */)
    } else {
        TwoPane(/* ... */)
    }
}

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

Рис. 3. Узкая карточка, показывающая только значок и заголовок, и более широкая карточка, показывающая значок, заголовок и краткое описание.

Не пытайтесь использовать размер фактического экрана устройства. Это не будет точным для разных типов экранов, а также не будет точным, если приложение не полноэкранное.

Поскольку составной объект не является компонуемым на уровне контента, не используйте метрики текущего окна напрямую. Если компонент размещен с отступом (например, со вставками) или если приложение включает в себя такие компоненты, как направляющие навигации или панели приложения, объем отображаемого пространства, доступного для составного объекта, может значительно отличаться от общего пространства, доступного приложению.

Используйте ширину, которую фактически задает составной элемент, для визуализации самого себя. У вас есть два варианта получить эту ширину:

  • Если вы хотите изменить место и способ отображения контента, используйте набор модификаторов или собственный макет, чтобы сделать макет адаптивным. Это может быть так же просто, как заставить дочернего элемента заполнить все доступное пространство или разместить дочерние элементы в нескольких столбцах, если места достаточно.

  • Если вы хотите изменить то, что показываете, используйте BoxWithConstraints как более мощную альтернативу. BoxWithConstraints предоставляет ограничения измерения , которые можно использовать для вызова различных компонуемых объектов в зависимости от доступного пространства отображения. Однако это требует определенных затрат, поскольку BoxWithConstraints откладывает композицию до этапа макета, когда эти ограничения известны, что приводит к увеличению объема работы во время макета.

@Composable
fun Card(/* ... */) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(/* ... */)
                Title(/* ... */)
            }
        } else {
            Row {
                Column {
                    Title(/* ... */)
                    Description(/* ... */)
                }
                Image(/* ... */)
            }
        }
    }
}

Убедитесь, что все данные доступны для дисплеев разных размеров.

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

Однако это противоречит принципу однонаправленного потока данных, согласно которому данные могут быть подняты и предоставлены компонуемым объектам для надлежащего рендеринга. Для составного объекта должно быть предоставлено достаточно данных, чтобы в составном объекте всегда было достаточно контента для любого размера дисплея, даже если некоторая часть контента может не всегда использоваться.

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(description)
                }
                Image(imageUrl)
            }
        }
    }
}

Опираясь на пример Card , обратите внимание, что description всегда передается в Card . Несмотря на то, что description используется только тогда, когда ширина позволяет его отобразить, Card всегда требует description , независимо от доступной ширины.

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

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

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    var showMore by remember { mutableStateOf(false) }

    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(
                        description = description,
                        showMore = showMore,
                        onShowMoreToggled = { newValue ->
                            showMore = newValue
                        }
                    )
                }
                Image(imageUrl)
            }
        }
    }
}

Узнать больше

Чтобы узнать больше об адаптивных макетах в Compose, посетите следующие ресурсы:

Примеры приложений

  • CanonicalLayouts — это хранилище проверенных шаблонов проектирования, обеспечивающих оптимальное взаимодействие с пользователем на больших дисплеях.
  • JetNews показывает, как разработать приложение, которое адаптирует свой пользовательский интерфейс для использования доступного пространства дисплея.
  • Ответ – это адаптивный образец для поддержки мобильных устройств, планшетов и складных устройств.
  • Теперь в Android есть приложение, использующее адаптивные макеты для поддержки дисплеев разных размеров.

Видео

,

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

Чтобы поддерживать как можно больше размеров дисплеев — будь то разные экраны устройств или разные окна приложений в многооконном режиме — создавайте макеты приложений так, чтобы они были отзывчивыми и адаптивными. Адаптивные/адаптивные макеты обеспечивают оптимизированное взаимодействие с пользователем независимо от размера дисплея, позволяя вашему приложению работать с телефонами, планшетами, складными устройствами, устройствами ChromeOS, книжной и альбомной ориентацией, а также с изменяемыми конфигурациями дисплея, такими как режим разделенного экрана и оконное оформление рабочего стола.

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

Jetpack Compose как декларативный набор инструментов пользовательского интерфейса идеально подходит для проектирования и реализации макетов, которые динамически изменяются для различного отображения контента на дисплеях разных размеров.

Вносите явные большие изменения в макет для составных элементов на уровне контента.

Компоненты уровня приложения и контента занимают все пространство дисплея, доступное вашему приложению. Для этих типов компонуемых объектов может иметь смысл изменить общий макет вашего приложения на больших дисплеях.

Избегайте использования значений физического оборудования для принятия решений по компоновке. Может возникнуть соблазн принять решения, основанные на фиксированной материальной ценности (является ли устройство планшетом? Имеет ли физический экран определенное соотношение сторон?), но ответы на эти вопросы могут оказаться бесполезными для определения пространства, доступного для вашего пользовательского интерфейса. .

Рис. 1. Форм-факторы телефона, складного устройства, планшета и ноутбука

На планшетах приложение может работать в многооконном режиме, то есть оно может разделять экран с другим приложением. В оконном режиме рабочего стола или в ChromeOS приложение может находиться в окне изменяемого размера. Физических экранов может быть даже несколько, например, в складном устройстве. Во всех этих случаях физический размер экрана не имеет значения для принятия решения о том, как отображать контент.

Вместо этого вам следует принимать решения на основе фактической части экрана, выделенной вашему приложению, описываемой текущими метриками окна, предоставляемыми библиотекой Jetpack WindowManager . Пример использования WindowManager в приложении Compose см. в образце JetNews .

Адаптация макетов к доступному пространству дисплея также уменьшает объем специальной обработки, необходимой для поддержки таких платформ, как ChromeOS, и форм-факторов, таких как планшеты и складные устройства.

Определив метрики пространства, доступного для вашего приложения, преобразуйте необработанный размер в класс размера окна, как описано в разделе Использование классов размера окна . Классы размеров окон — это точки останова, предназначенные для того, чтобы сбалансировать простоту логики приложения с гибкостью оптимизации вашего приложения для большинства размеров дисплея. Классы размера окна относятся к общему окну вашего приложения, поэтому используйте классы для решений по макету, которые влияют на общий макет вашего приложения. Вы можете передать классы размера окна как состояние или выполнить дополнительную логику для создания производного состояния для передачи вложенным составным объектам.

@Composable
fun MyApp(
    windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
) {
    // Perform logic on the size class to decide whether to show the top app bar.
    val showTopAppBar = windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT

    // MyScreen knows nothing about window sizes, and performs logic based on a Boolean flag.
    MyScreen(
        showTopAppBar = showTopAppBar,
        /* ... */
    )
}

Многоуровневый подход ограничивает логику размера дисплея одним местом вместо того, чтобы разбрасывать ее по всему приложению во многих местах, которые необходимо синхронизировать. Одно местоположение создает состояние, которое может быть явно передано другим составным объектам, как и любое другое состояние приложения. Явная передача состояния упрощает отдельные составные объекты, поскольку они принимают класс размера окна или указанную конфигурацию вместе с другими данными.

Гибкие вложенные составные элементы можно использовать повторно.

Составные элементы более пригодны для повторного использования, если их можно разместить в самых разных местах. Если составной объект необходимо разместить в определенном месте с определенным размером, маловероятно, что составной объект можно будет повторно использовать в других контекстах. Это также означает, что отдельные, многократно используемые составные элементы должны избегать неявной зависимости от глобальной информации о размере дисплея.

Представьте себе вложенный составной объект, реализующий макет списка , который может отображать либо одну панель, либо две панели рядом:

Приложение, показывающее две панели рядом.
Рис. 2. Приложение, демонстрирующее типичный макет списка: 1 — область списка; 2 , область детализации.

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

@Composable
fun AdaptivePane(
    showOnePane: Boolean,
    /* ... */
) {
    if (showOnePane) {
        OnePane(/* ... */)
    } else {
        TwoPane(/* ... */)
    }
}

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

Рис. 3. Узкая карточка, показывающая только значок и заголовок, и более широкая карточка, показывающая значок, заголовок и краткое описание.

Не пытайтесь использовать размер фактического экрана устройства. Это не будет точным для разных типов экранов, а также не будет точным, если приложение не полноэкранное.

Поскольку составной объект не является компонуемым на уровне контента, не используйте метрики текущего окна напрямую. Если компонент размещен с отступом (например, со вставками) или если приложение включает в себя такие компоненты, как направляющие навигации или панели приложения, объем отображаемого пространства, доступного для составного объекта, может значительно отличаться от общего пространства, доступного приложению.

Используйте ширину, которую фактически задает составной элемент, для визуализации самого себя. У вас есть два варианта получить эту ширину:

  • Если вы хотите изменить место и способ отображения контента, используйте набор модификаторов или собственный макет, чтобы сделать макет адаптивным. Это может быть так же просто, как заставить дочернего элемента заполнить все доступное пространство или расположить дочерние элементы в нескольких столбцах, если места достаточно.

  • Если вы хотите изменить то, что показываете, используйте BoxWithConstraints как более мощную альтернативу. BoxWithConstraints предоставляет ограничения измерения , которые можно использовать для вызова различных компонуемых объектов в зависимости от доступного пространства отображения. Однако это требует определенных затрат, поскольку BoxWithConstraints откладывает композицию до этапа макета, когда эти ограничения известны, что приводит к увеличению объема работы во время макета.

@Composable
fun Card(/* ... */) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(/* ... */)
                Title(/* ... */)
            }
        } else {
            Row {
                Column {
                    Title(/* ... */)
                    Description(/* ... */)
                }
                Image(/* ... */)
            }
        }
    }
}

Убедитесь, что все данные доступны для дисплеев разных размеров.

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

Однако это противоречит принципу однонаправленного потока данных, согласно которому данные могут быть подняты и предоставлены компонуемым объектам для надлежащего рендеринга. Для составного объекта должно быть предоставлено достаточно данных, чтобы в составном объекте всегда было достаточно контента для любого размера дисплея, даже если некоторая часть контента может не всегда использоваться.

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(description)
                }
                Image(imageUrl)
            }
        }
    }
}

Опираясь на пример Card , обратите внимание, что description всегда передается в Card . Несмотря на то, что description используется только тогда, когда ширина позволяет его отобразить, Card всегда требует description , независимо от доступной ширины.

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

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

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    var showMore by remember { mutableStateOf(false) }

    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(
                        description = description,
                        showMore = showMore,
                        onShowMoreToggled = { newValue ->
                            showMore = newValue
                        }
                    )
                }
                Image(imageUrl)
            }
        }
    }
}

Узнать больше

Чтобы узнать больше об адаптивных макетах в Compose, посетите следующие ресурсы:

Примеры приложений

  • CanonicalLayouts — это хранилище проверенных шаблонов проектирования, обеспечивающих оптимальное взаимодействие с пользователем на больших дисплеях.
  • JetNews показывает, как разработать приложение, которое адаптирует свой пользовательский интерфейс для использования доступного пространства дисплея.
  • Ответ – это адаптивный образец для поддержки мобильных устройств, планшетов и складных устройств.
  • Теперь в Android есть приложение, использующее адаптивные макеты для поддержки дисплеев разных размеров.

Видео