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 merujuk ke informasi berikut:

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

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

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

Mengaktifkan 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 sesuai dengan 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 posisi 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 mewakili ukuran jendela saat ini dengan nilai minimum yang ditentukan dalam class ukuran jendela. Contoh berikut mengubah jumlah panel sesuai dengan lebar jendela. WindowSizeClass class memiliki konstanta untuk nilai minimum class ukuran jendela (Gambar 1).

Fungsi derivedMediaQuery mengevaluasi lambda query dan menggabungkan 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 posisi jendela

Parameter windowPosture menjelaskan posisi jendela saat ini sebagai objek UiMediaScope.Posture. Anda dapat memeriksa posisi saat ini dengan membandingkan parameter dengan nilai yang ditentukan dalam class UiMediaScope.Posture. Contoh berikut mengalihkan tata letak sesuai dengan posisi 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 dengan presisi tinggi membantu pengguna menunjuk elemen UI dengan tepat. 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 akan diselesaikan dengan presisi tertinggi. Misalnya, jika ada dua perangkat penunjuk — perangkat presisi Fine dan perangkat presisi BluntFine adalah nilai parameter pointerPrecision.

Contoh berikut menampilkan 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 akan diselesaikan sebagai Physical. Jika tidak ada yang 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 menampilkan tombol untuk 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 menjadi 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 meningkatkan 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. Mengaktifkan fungsi mediaQuery.
  2. Menentukan objek kustom yang mengimplementasikan antarmuka UiMediaScope.
  3. Menetapkan objek kustom ke LocalUiMediaScope dengan fungsi CompositionLocalProvider.
  4. Memanggil composable untuk melihat pratinjau dalam 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()
        }
    }
}