Mengkueri informasi untuk tata letak adaptif dengan mediaQuery

Anda memerlukan berbagai jenis informasi, seperti kemampuan perangkat dan status aplikasi, untuk memperbarui tata letak aplikasi. Lebar dan tinggi jendela adalah informasi yang paling umum digunakan. Selain itu, Anda dapat melihat informasi berikut:

  • Posisi jendela
  • Presisi perangkat penunjuk
  • Jenis keyboard
  • Apakah kamera dan mikrofon didukung oleh perangkat
  • Jarak antara pengguna dan layar perangkat

Karena informasi diperbarui secara dinamis, Anda perlu memantaunya dan memicu rekomposisi saat ada pembaruan. Fungsi mediaQuery mengabstraksi detail pengambilan informasi dan memungkinkan Anda berfokus pada penentuan kondisi untuk memicu update tata letak. Contoh berikut mengalihkan tata letak ke TabletopLayout saat postur perangkat foldable adalah tabletop:

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

Aktifkan fungsi mediaQuery

Untuk mengaktifkan fungsi mediaQuery, tetapkan atribut isMediaQueryIntegrationEnabled dari objek ComposeUiFlags ke true:

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

Menentukan kondisi dengan parameter

Anda dapat menentukan kondisi sebagai lambda yang dievaluasi dalam UiMediaScope. Fungsi mediaQuery mengevaluasi kondisi berdasarkan status saat ini dan kemampuan perangkat. Fungsi ini menampilkan nilai boolean, sehingga Anda dapat menentukan tata letak dengan cabang kondisional seperti ekspresi if. Tabel 1 menjelaskan parameter yang tersedia di UiMediaScope.

Parameter Jenis nilai Deskripsi
windowWidth Dp Lebar jendela saat ini dalam dp.
windowHeight Dp Tinggi jendela saat ini dalam dp.
windowPosture UiMediaScope.Posture Posisi jendela aplikasi saat ini.
pointerPrecision UiMediaScope.PointerPrecision Presisi tertinggi dari perangkat penunjuk yang tersedia.
keyboardKind UiMediaScope.KeyboardKind Jenis keyboard yang tersedia atau terhubung.
hasCamera Boolean Apakah kamera didukung di perangkat.
hasMicrophone Boolean Apakah mikrofon didukung di perangkat.
viewingDistance UiMediaScope.ViewingDistance Jarak umum antara pengguna dan layar perangkat.

Objek UiMediaScope menyelesaikan nilai parameter. Fungsi mediaQuery menggunakan LocalUiMediaScope.current untuk mengakses objek UiMediaScope, yang mewakili kemampuan dan konteks perangkat saat ini. Objek ini diperbarui secara dinamis saat ada perubahan, seperti saat pengguna mengubah postur perangkat. Fungsi mediaQuery kemudian mengevaluasi lambda query dengan objek UiMediaScope yang diperbarui dan menampilkan nilai boolean. Misalnya, cuplikan berikut memilih antara TabletopLayout dan FlatLayout berdasarkan nilai parameter windowPosture.

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

Membuat keputusan berdasarkan ukuran jendela

Class ukuran jendela adalah sekumpulan titik henti sementara area pandang tidak berubah yang membantu Anda mendesain, mengembangkan, dan menguji tata letak adaptif. Anda dapat membandingkan dua parameter yang merepresentasikan ukuran jendela saat ini dengan nilai minimum yang ditentukan dalam class ukuran jendela. Contoh berikut mengubah jumlah panel sesuai dengan lebar jendela. Class WindowSizeClass memiliki konstanta untuk nilai minimum class ukuran jendela (Gambar 1).

Fungsi derivedMediaQuery mengevaluasi lambda query dan membungkus hasilnya dalam derivedStateOf. Karena windowWidth dan windowHeight dapat sering diperbarui, panggil fungsi derivedMediaQuery, bukan fungsi mediaQuery saat Anda merujuk ke parameter tersebut dalam lambda query.

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

Gambar 1. Tata letak diperbarui sesuai dengan lebar jendela.

Memperbarui tata letak sesuai dengan postur jendela

Parameter windowPosture menjelaskan postur jendela saat ini sebagai objek UiMediaScope.Posture. Anda dapat memeriksa postur saat ini dengan membandingkan parameter dengan nilai yang ditentukan dalam class UiMediaScope.Posture. Contoh berikut mengganti tata letak sesuai dengan postur jendela:

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

Memeriksa presisi perangkat penunjuk yang tersedia

Perangkat penunjuk presisi tinggi membantu pengguna menunjuk elemen UI secara presisi. Presisi perangkat penunjuk bergantung pada jenis perangkat.

Parameter pointerPrecision menjelaskan presisi perangkat penunjuk yang tersedia, seperti mouse dan layar sentuh. Ada empat nilai yang ditentukan dalam class UiMediaScope.PointerPrecision: Fine, Coarse, Blunt, dan None. None berarti tidak ada perangkat penunjuk yang tersedia. Presisi berkisar dari tertinggi hingga terendah dalam urutan ini: Fine, Coarse, dan Blunt.

Jika beberapa perangkat penunjuk tersedia dan presisinya berbeda, parameter diselesaikan dengan presisi tertinggi. Misalnya, jika ada dua perangkat penunjuk — perangkat presisi Fine dan perangkat presisi BluntFine adalah nilai parameter pointerPrecision.

Contoh berikut menunjukkan tombol yang lebih besar saat pengguna menggunakan perangkat penunjuk dengan presisi rendah:

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

Memeriksa jenis keyboard yang tersedia

Parameter keyboardKind mewakili jenis keyboard yang tersedia: Physical, Virtual, dan None. Jika keyboard virtual ditampilkan dan keyboard hardware tersedia pada saat yang sama, parameter diselesaikan sebagai Physical. Jika keduanya tidak terdeteksi, None adalah nilai parameter. Contoh berikut menampilkan pesan yang menyarankan pengguna untuk menghubungkan keyboard jika tidak ada keyboard yang terdeteksi:

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

Memeriksa apakah perangkat mendukung kamera dan mikrofon

Beberapa perangkat tidak mendukung kamera atau mikrofon. Anda dapat memeriksa apakah perangkat mendukung kamera dan mikrofon dengan parameter hasCamera dan parameter hasMicrophone. Contoh berikut menunjukkan tombol yang akan digunakan dengan kamera dan mikrofon saat perangkat mendukungnya:

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

Menyesuaikan UI dengan perkiraan jarak pandang

Jarak pandang adalah faktor yang membantu menentukan tata letak. Jika pengguna menggunakan aplikasi dari jarak jauh, mereka akan mengharapkan teks dan elemen UI lebih besar. Parameter viewingDistance memberikan perkiraan jarak pandang berdasarkan jenis perangkat dan konteks penggunaan umumnya.

Ada tiga nilai yang ditentukan dalam class UiMediaScope.ViewingDistance: Near, Medium, dan Far. Near berarti layar berada dalam jarak dekat, dan Far berarti perangkat dilihat dari jarak jauh. Contoh berikut memperbesar ukuran font saat jarak pandang adalah Far atau Medium:

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

Melihat pratinjau komponen UI

Anda dapat memanggil fungsi mediaQuery dan derivedMediaQuery dalam fungsi composable untuk melihat pratinjau komponen UI. Cuplikan berikut memilih antara TabletopLayout dan FlatLayout berdasarkan nilai parameter windowPosture. Untuk melihat pratinjau TabletopLayout, parameter windowPosture harus berupa UiMediaScope.Posture.Tabletop.

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

Fungsi mediaQuery dan derivedMediaQuery mengevaluasi lambda query yang diberikan dalam objek UiMediaScope, yang disediakan sebagai LocalUiMediaScope.current. Anda dapat menggantinya dengan langkah-langkah berikut:

  1. Aktifkan fungsi mediaQuery.
  2. Tentukan objek kustom yang mengimplementasikan antarmuka UiMediaScope.
  3. Tetapkan objek kustom ke LocalUiMediaScope dengan fungsi CompositionLocalProvider.
  4. Panggil composable untuk melihat pratinjau di lambda konten fungsi CompositionLocalProvider.

Anda dapat melihat pratinjau TabletopLayout dengan contoh berikut:

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