mediaQuery를 사용하여 적응형 레이아웃 정보 쿼리

앱 레이아웃을 업데이트하려면 기기 기능, 앱 상태 등 다양한 유형의 정보가 필요합니다. 창 너비와 높이는 가장 일반적으로 사용되는 정보입니다. 또한 다음 정보를 참고할 수 있습니다.

  • 창 자세
  • 포인팅 기기 정밀도
  • 키보드 유형
  • 기기에서 카메라와 마이크를 지원하는지 여부
  • 사용자와 기기 디스플레이 간의 거리

정보가 동적으로 업데이트되므로 업데이트가 발생하면 이를 모니터링하고 리컴포지션을 트리거해야 합니다. mediaQuery 함수는 정보 검색의 세부정보를 추상화하여 레이아웃 업데이트를 트리거하는 조건을 정의하는 데 집중할 수 있도록 합니다. 다음 예에서는 폴더블 자세가 테이블 모드일 때 레이아웃을 TabletopLayout로 전환합니다.

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

mediaQuery 함수 사용 설정

mediaQuery 함수를 사용 설정하려면 ComposeUiFlags 객체의 isMediaQueryIntegrationEnabled 속성을 true로 설정합니다.

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

매개변수를 사용하여 조건 정의

UiMediaScope 내에서 평가되는 람다로 조건을 정의할 수 있습니다. mediaQuery 함수는 현재 상태와 기기 기능에 따라 조건을 평가합니다. 이 함수는 불리언 값을 반환하므로 if 표현식과 같은 조건부 분기로 레이아웃을 결정할 수 있습니다. 표 1에서는 UiMediaScope에서 사용할 수 있는 파라미터를 설명합니다.

매개변수 값 유형 설명
windowWidth Dp 현재 창 너비(dp)입니다.
windowHeight Dp 현재 창 높이(dp)입니다.
windowPosture UiMediaScope.Posture 애플리케이션 창의 현재 자세입니다.
pointerPrecision UiMediaScope.PointerPrecision 사용 가능한 포인팅 기기의 최고 정밀도입니다.
keyboardKind UiMediaScope.KeyboardKind 사용 가능하거나 연결된 키보드의 유형입니다.
hasCamera Boolean 기기에서 카메라가 지원되는지 여부입니다.
hasMicrophone Boolean 기기에서 마이크가 지원되는지 여부입니다.
viewingDistance UiMediaScope.ViewingDistance 사용자와 기기 화면 간의 일반적인 거리입니다.

UiMediaScope 객체는 매개변수의 값을 확인합니다. mediaQuery 함수는 LocalUiMediaScope.current를 사용하여 현재 기기 기능과 컨텍스트를 나타내는 UiMediaScope 객체에 액세스합니다. 이 객체는 사용자가 기기 자세를 변경하는 등 변경사항이 발생하면 동적으로 업데이트됩니다. 그런 다음 mediaQuery 함수는 업데이트된 UiMediaScope 객체로 query 람다를 평가하고 불리언 값을 반환합니다. 예를 들어 다음 스니펫은 windowPosture 매개변수 값을 기반으로 TabletopLayoutFlatLayout 중에서 선택합니다.

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

창 크기에 따라 결정

창 크기 클래스는 적응형 레이아웃을 디자인하고 개발 및 테스트할 수 있는 체계적인 표시 영역 중단점입니다. 현재 창 크기를 나타내는 두 매개변수를 창 크기 클래스에 정의된 임계값과 비교할 수 있습니다. 다음 예에서는 창 너비에 따라 창 수를 변경합니다. WindowSizeClass 클래스에는 창 크기 클래스의 기준점 상수가 있습니다 (그림 1).

derivedMediaQuery 함수는 query 람다를 평가하고 결과를 derivedStateOf로 래핑합니다. windowWidthwindowHeight는 자주 업데이트될 수 있으므로 query 람다에서 이러한 매개변수를 참조할 때는 mediaQuery 함수 대신 derivedMediaQuery 함수를 호출하세요.

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()
}

사용 가능한 포인팅 기기의 정밀도 확인

정밀도가 높은 포인팅 기기를 사용하면 사용자가 UI 요소를 정확하게 가리킬 수 있습니다. 포인팅 기기의 정밀도는 기기 유형에 따라 다릅니다.

pointerPrecision 파라미터는 마우스, 터치 스크린 등 사용 가능한 포인팅 기기의 정밀도를 설명합니다. UiMediaScope.PointerPrecision 클래스에는 Fine, Coarse, Blunt, None의 네 가지 값이 정의되어 있습니다. None은 포인팅 기기를 사용할 수 없음을 의미합니다. 정밀도는 Fine, Coarse, Blunt 순으로 높습니다.

사용 가능한 포인팅 기기가 여러 개이고 정밀도가 다른 경우 매개변수는 가장 높은 정밀도로 확인됩니다. 예를 들어 포인팅 기기가 두 개(Fine 정밀도 기기와 Blunt 정밀도 기기) 있는 경우 FinepointerPrecision 매개변수의 값입니다.

다음 예시는 사용자가 정밀도가 낮은 포인팅 기기를 사용하는 경우 더 큰 버튼을 보여줍니다.

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()
    }
}

예상 시청 거리로 UI 조정

시청 거리는 레이아웃을 결정하는 데 도움이 되는 요소입니다. 사용자가 멀리서 앱을 사용하는 경우 텍스트와 UI 요소가 더 커야 합니다. 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
}

UI 구성요소 미리보기

구성 가능한 함수에서 mediaQueryderivedMediaQuery 함수를 호출하여 UI 구성요소를 미리 볼 수 있습니다. 다음 스니펫은 windowPosture 매개변수 값을 기반으로 TabletopLayoutFlatLayout 중에서 선택합니다. TabletopLayout를 미리 보려면 windowPosture 매개변수가 UiMediaScope.Posture.Tabletop여야 합니다.

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

mediaQueryderivedMediaQuery 함수는 LocalUiMediaScope.current로 제공되는 UiMediaScope 객체 내에서 주어진 query 람다를 평가합니다. 다음 단계에 따라 재정의할 수 있습니다.

  1. mediaQuery 함수를 사용 설정합니다.
  2. UiMediaScope 인터페이스를 구현하는 맞춤 객체를 정의합니다.
  3. CompositionLocalProvider 함수를 사용하여 맞춤 객체를 LocalUiMediaScope로 설정합니다.
  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()
        }
    }
}