Запрос информации для адаптивных макетов с помощью mediaQuery

Для обновления макета приложения вам потребуется различная информация, например, возможности устройства и статус приложения. Чаще всего используются ширина и высота окна. Кроме того, вы можете использовать следующую информацию:

  • Положение окна
  • Точность указательных устройств
  • Тип клавиатуры
  • Поддерживаются ли камера и микрофон устройством.
  • Расстояние между пользователем и экраном устройства.

Поскольку информация обновляется динамически, необходимо отслеживать её и запускать перекомпоновку при каждом обновлении. Функция mediaQuery абстрагирует детали получения информации и позволяет сосредоточиться на определении условия для запуска обновления макета. В следующем примере макет переключается на TabletopLayout , когда складное положение — «стол»:

@Composable
fun VideoPlayer(
    // ...
) {
    // ...
            if (mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }) {
                TabletopLayout()
            } else {
                FlatLayout()
            }
    // ...
}

Включите функцию mediaQuery

Чтобы включить функцию mediaQuery , установите атрибут isMediaQueryIntegrationEnabled объекта ComposeUiFlags в true :

class MyApplication : Application() {
    override fun onCreate() {
        ComposeUiFlags.isMediaQueryIntegrationEnabled = true
        super.onCreate()
    }
}

Определите условие с параметрами.

Условие можно определить как лямбда-функцию, которая оценивается внутри UiMediaScope . Функция mediaQuery оценивает условие в соответствии с текущим состоянием и возможностями устройства. Функция возвращает логическое значение, поэтому вы можете определять макет с помощью условных ветвей, как в выражении if . В таблице 1 описаны параметры, доступные в UiMediaScope .

Параметр Тип значения Описание
windowWidth Dp Текущая ширина окна в децибелах.
windowHeight Dp Текущая высота окна в децибелах.
windowPosture UiMediaScope.Posture Текущее состояние окна приложения.
pointerPrecision UiMediaScope.PointerPrecision Максимальная точность среди доступных устройств наведения.
keyboardKind UiMediaScope.KeyboardKind Тип доступной или подключенной клавиатуры.
hasCamera Boolean Поддерживается ли камера на устройстве.
hasMicrophone Boolean Поддерживается ли микрофон на устройстве.
viewingDistance UiMediaScope.ViewingDistance Типичное расстояние между пользователем и экраном устройства.

Объект UiMediaScope определяет значения параметров. Функция mediaQuery использует LocalUiMediaScope.current для доступа к объекту UiMediaScope , который представляет текущие возможности и контекст устройства. Этот объект динамически обновляется при любых изменениях, например, когда пользователь изменяет состояние устройства. Затем функция mediaQuery вычисляет лямбда- query с обновленным объектом UiMediaScope и возвращает логическое значение. Например, следующий фрагмент кода выбирает между TabletopLayout и FlatLayout на основе значения параметра windowPosture .

@Composable
fun VideoPlayer(
    // ...
) {
    // ...
            if (mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }) {
                TabletopLayout()
            } else {
                FlatLayout()
            }
    // ...
}

Принимайте решение, исходя из размера окна.

Классы размеров окон представляют собой набор заданных контрольных точек области просмотра, которые помогают проектировать, разрабатывать и тестировать адаптивные макеты. Вы можете сравнить два параметра, представляющие текущий размер окна, с пороговым значением, определенным в классах размеров окон. В следующем примере количество панелей изменяется в зависимости от ширины окна. Класс WindowSizeClass содержит константы для пороговых значений классов размеров окон (Рисунок 1).

Функция derivedMediaQuery вычисляет лямбда query и оборачивает результат в derivedStateOf . Поскольку windowWidth и windowHeight могут часто обновляться, при обращении к этим параметрам в лямбда- query следует вызывать функцию derivedMediaQuery вместо функции mediaQuery .

val narrowerThanMedium by derivedMediaQuery {
    windowWidth < WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND.dp
}
val narrowerThanExpanded by derivedMediaQuery {
    windowWidth < WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND.dp
}
when {
    narrowerThanMedium -> SinglePaneLayout()
    narrowerThanExpanded -> TwoPaneLayout()
    else -> ThreePaneLayout()
}

Рисунок 1. Макет обновляется в соответствии с шириной окна.

Обновите макет в соответствии с положением окна.

Параметр windowPosture описывает текущее положение окна в виде объекта UiMediaScope.Posture . Вы можете проверить текущее положение , сравнив значение параметра со значениями, определенными в классе UiMediaScope.Posture . В следующем примере переключение макета происходит в зависимости от положения окна:

when {
    mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
}

Проверьте точность имеющегося указывающего устройства.

Высокоточное указательное устройство помогает пользователям точно наводить курсор на элемент пользовательского интерфейса. Точность указательного устройства зависит от его типа.

Параметр pointerPrecision описывает точность доступных устройств ввода, таких как мышь и сенсорный экран. В классе UiMediaScope.PointerPrecision определены четыре значения: Fine , Coarse , Blunt и None . None означает, что ни одно устройство ввода недоступно. Точность варьируется от наибольшей к наименьшей в следующем порядке: Fine , Coarse и Blunt .

Если доступно несколько устройств ввода, и их точность различна, параметр определяется по наибольшему значению. Например, если есть два устройства ввода — устройство с высокой точностью Fine и устройство с низкой точностью Blunt — значением параметра pointerPrecision будет Fine .

В следующем примере показана кнопка большего размера, когда пользователь использует указательное устройство с низкой точностью:

if (mediaQuery { pointerPrecision == UiMediaScope.PointerPrecision.Blunt }) {
    LargeSizeButton()
} else {
    NormalSizeButton()
}

Проверьте доступные типы клавиатур.

Параметр keyboardKind представляет тип доступных клавиатур: Physical , Virtual и None . Если отображается экранная клавиатура и одновременно доступна аппаратная клавиатура, параметр определяется как Physical . Если ни одна из них не обнаружена, значение параметра — None . В следующем примере показано сообщение, предлагающее пользователям подключить клавиатуру, если ни одна клавиатура не обнаружена:

if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.None }) {
    SuggestKeyboardConnect()
}

Проверьте, поддерживает ли устройство камеру и микрофон.

Некоторые устройства не поддерживают камеры или микрофоны. Вы можете проверить, поддерживает ли устройство камеру и микрофон, с помощью параметров hasCamera и hasMicrophone . В следующем примере показаны кнопки для использования с камерой и микрофоном, если устройство их поддерживает:

Row {
    OutlinedTextField(state = rememberTextFieldState())
    // Show the MicButton when the device supports a microphone.
    if (mediaQuery { hasMicrophone }) {
        MicButton()
    }
    // Show the CameraButton when the device supports a camera.
    if (mediaQuery { hasCamera }) {
        CameraButton()
    }
}

Настройте пользовательский интерфейс с учетом предполагаемого расстояния просмотра.

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

В классе UiMediaScope.ViewingDistance определены три значения: Near , Medium и Far . Near означает, что экран находится в непосредственной близости, а Far — что устройство просматривается с расстояния. В следующем примере размер шрифта увеличивается, когда расстояние просмотра равно Far или Medium :

val fontSize = when {
    mediaQuery { viewingDistance == UiMediaScope.ViewingDistance.Far } -> 20.sp
    mediaQuery { viewingDistance == UiMediaScope.ViewingDistance.Medium } -> 18.sp
    else -> 16.sp
}

Предварительный просмотр компонента пользовательского интерфейса

Вы можете вызывать функции mediaQuery и derivedMediaQuery в компонуемых функциях для предварительного просмотра компонентов пользовательского интерфейса. Следующий фрагмент кода выбирает между TabletopLayout и FlatLayout на основе значения параметра windowPosture . Для предварительного просмотра TabletopLayout параметр windowPosture должен быть UiMediaScope.Posture.Tabletop .

when {
    mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
}

Функции mediaQuery и derivedMediaQuery вычисляют заданный лямбда query внутри объекта UiMediaScope , который предоставляется как LocalUiMediaScope.current . Вы можете переопределить его, выполнив следующие шаги:

  1. Включите функцию mediaQuery .
  2. Определите пользовательский объект, реализующий интерфейс UiMediaScope .
  3. Установите пользовательский объект в качестве LocalUiMediaScope с помощью функции CompositionLocalProvider .
  4. Вызовите компонент для предварительного просмотра в лямбда-функции содержимого функции CompositionLocalProvider .

Предварительный просмотр TabletopLayout можно посмотреть на следующем примере:

@Preview
@Composable
fun PreviewLayoutForTabletop() {
    // Step 1: Enable the mediaQuery function
    ComposeUiFlags.isMediaQueryIntegrationEnabled = true

    val currentUiMediaScope = LocalUiMediaScope.current
    // Step 2: Define a custom object implementing the UiMediaScope interface.
    // The object overrides the windowPosture parameter.
    // The resolution of the remaining parameters is deferred to the currentUiMediaScope object.
    val uiMediaScope = remember(currentUiMediaScope) {
        object : UiMediaScope by currentUiMediaScope {
            override val windowPosture: UiMediaScope.Posture = UiMediaScope.Posture.Tabletop
        }
    }

    // Step 3: Set the object to the LocalUiMediaScope.
    CompositionLocalProvider(LocalUiMediaScope provides uiMediaScope) {
        // Step 4: Call the composable to preview.
        when {
            mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
            mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
            mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
        }
    }
}