Grafis dalam Compose

Jetpack Compose mempermudah penggunaan grafis kustom. Banyak aplikasi harus dapat mengontrol dengan tepat apa yang digambar di layar. Hal ini mungkin semudah menempatkan kotak atau lingkaran di layar di tempat yang tepat, atau mungkin pengaturan elemen grafis yang rumit dalam banyak gaya yang berbeda. Dengan pendekatan deklaratif Compose, semua konfigurasi grafis terjadi di satu tempat, bukan dipisahkan antara pemanggilan metode dan objek helper Paint. Compose menangani pembuatan dan update objek yang diperlukan dengan cara yang efisien.

Grafis deklaratif dengan Compose

Compose memperluas pendekatan deklaratifnya tentang cara menangani grafis. Pendekatan Compose menawarkan sejumlah keuntungan:

  • Compose akan meminimalkan status dalam elemen grafisnya, membantu Anda menghindari kesulitan pemrograman status.
  • Jika Anda menggambar sesuatu, semua opsi tepat di tempat yang Anda harapkan, dalam fungsi composable.
  • API grafis Compose menangani pembuatan dan pengosongan objek dengan cara yang efisien.

Canvas

Composable inti untuk grafis kustom adalah Canvas. Anda menempatkan Canvas di tata letak dengan cara yang sama seperti elemen UI Compose lainnya. Dalam Canvas, Anda dapat menggambar elemen dengan kontrol yang akurat terhadap gaya dan lokasinya.

Misalnya, kode ini membuat composable Canvas yang mengisi semua ruang yang tersedia dalam elemen induknya:

Canvas(modifier = Modifier.fillMaxSize()) {
}

Canvas secara otomatis menampilkan DrawScope, lingkungan gambar cakupan yang mempertahankan statusnya sendiri. Ini memungkinkan Anda menetapkan parameter untuk sekelompok elemen grafis. DrawScope menyediakan beberapa kolom yang berguna, seperti size, objek Size yang menentukan dimensi DrawScope saat ini dan maksimum.

Jadi, misalnya Anda ingin menggambar garis diagonal dari sudut kanan atas kanvas ke sudut kiri bawah. Anda dapat melakukannya dengan menambahkan perintah drawLine:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasWidth = size.width
    val canvasHeight = size.height

    drawLine(
        start = Offset(x = canvasWidth, y = 0f),
        end = Offset(x = 0f, y = canvasHeight),
        color = Color.Blue
    )
}

Ponsel dengan garis tipis yang digambar secara diagonal di layar.

Gambar 1. Menggunakan drawLine untuk menggambar garis di kanvas. Kode ini menyetel warna garis, tetapi menggunakan lebar default.

Anda bisa menggunakan parameter lain untuk menyesuaikan gambar. Misalnya, secara default, garis digambar dengan lebar halus, yang ditampilkan sebagai lebar satu piksel terlepas dari skala gambar. Anda dapat mengganti nilai default dengan menyetel nilai strokeWidth:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasWidth = size.width
    val canvasHeight = size.height

    drawLine(
        start = Offset(x = canvasWidth, y = 0f),
        end = Offset(x = 0f, y = canvasHeight),
        color = Color.Blue,
        strokeWidth = 5F
    )
}

Ponsel dengan garis lebih tebal yang digambar secara diagonal di layar.

Gambar 2. Memodifikasi baris dari gambar 1 dengan mengganti lebar default.

Ada banyak fungsi menggambar sederhana lainnya, seperti drawRect dan drawCircle. Misalnya, kode ini menggambar lingkaran yang terisi di tengah kanvas, dengan diameter yang sama dengan setengah dimensi lebih pendek dari kanvas:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasWidth = size.width
    val canvasHeight = size.height
    drawCircle(
        color = Color.Blue,
        center = Offset(x = canvasWidth / 2, y = canvasHeight / 2),
        radius = size.minDimension / 4
    )
}

Ponsel dengan lingkaran biru yang berada di tengah layar.

Gambar 3. Menggunakan drawCircle untuk meletakkan lingkaran di tengah kanvas. Secara default, drawCircle menggambar lingkaran penuh, sehingga kita tidak perlu menentukan setelan tersebut secara eksplisit.

Fungsi gambar memiliki parameter default yang berguna. Misalnya, secara default drawRectangle() mengisi seluruh cakupan induknya, dan drawCircle() memiliki radius yang sama dengan setengah dari dimensi induknya yang lebih pendek. Seperti biasa dengan Kotlin, Anda dapat membuat kode jauh lebih sederhana dan lebih jelas dengan memanfaatkan nilai parameter default dan hanya menetapkan parameter yang perlu Anda ubah. Anda dapat memanfaatkan hal ini dengan memberikan parameter eksplisit ke metode menggambar DrawScope, karena elemen yang digambar akan mendasarkan setelan defaultnya pada setelan cakupan induk.

DrawScope

Seperti yang disebutkan, setiap Canvas Compose mengekspos DrawScope, lingkungan gambar tercakup, tempat Anda mengeluarkan perintah gambar.

Misalnya, kode berikut menggambar persegi panjang di sudut kiri atas kanvas:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasQuadrantSize = size / 2F
    drawRect(
        color = Color.Green,
        size = canvasQuadrantSize
    )
}

Anda dapat menggunakan fungsi DrawScope.inset() untuk menyesuaikan parameter default cakupan saat ini, mengubah batas gambar, dan menerjemahkan gambar sesuai dengan itu. Operasi seperti inset() berlaku untuk semua operasi gambar dalam lambda yang sesuai:

val canvasQuadrantSize = size / 2F
inset(50F, 30F) {
    drawRect(
        color = Color.Green,
        size = canvasQuadrantSize
    )
}

DrawScope menawarkan transformasi sederhana lainnya, seperti rotate(). Misalnya, kode berikut menggambar persegi panjang yang mengisi bagian tengah kanvas.

val canvasSize = size
val canvasWidth = size.width
val canvasHeight = size.height
drawRect(
    color = Color.Gray,
    topLeft = Offset(x = canvasWidth / 3F, y = canvasHeight / 3F),
    size = canvasSize / 3F
)

Ponsel dengan persegi panjang yang terisi di tengah layar.

Gambar 4. Menggunakan drawRect untuk menggambar persegi panjang yang terisi di tengah layar.

Anda dapat memutar persegi panjang dengan menerapkan rotasi ke DrawScope:

rotate(degrees = 45F) {
    drawRect(
        color = Color.Gray,
        topLeft = Offset(x = canvasWidth / 3F, y = canvasHeight / 3F),
        size = canvasSize / 3F
    )
}

Ponsel dengan persegi panjang yang diputar 45 derajat di tengah layar.

Gambar 5. Kami menggunakan rotate() untuk menerapkan rotasi ke cakupan gambar saat ini, yang memutar persegi panjang sebesar 45 derajat.

Jika Anda ingin menerapkan beberapa transformasi pada gambar, pendekatan terbaik adalah jangan membuat lingkungan bertingkat DrawScope. Sebagai gantinya, Anda harus menggunakan fungsi withTransform(), yang membuat dan menerapkan satu transformasi yang menggabungkan semua perubahan yang diinginkan. Menggunakan withTransform() lebih efisien daripada membuat panggilan bertingkat ke transformasi individual, karena semua transformasi dijalankan bersamaan dalam satu operasi, bukan Compose yang perlu menghitung dan menyimpan setiap transformasi bertingkat.

Misalnya, kode ini menerapkan terjemahan dan rotasi ke persegi panjang:

withTransform({
    translate(left = canvasWidth / 5F)
    rotate(degrees = 45F)
}) {
    drawRect(
        color = Color.Gray,
        topLeft = Offset(x = canvasWidth / 3F, y = canvasHeight / 3F),
        size = canvasSize / 3F
    )
}

Ponsel dengan kotak yang diputar bergeser ke sisi layar.

Gambar 6. Di sini kami menggunakan withTransform untuk menerapkan rotasi dan terjemahan, memutar persegi panjang, dan menggesernya ke kiri.