Selain composable Canvas
, Compose memiliki beberapa
Modifiers
grafik berguna yang membantu menggambar konten kustom. Pengubah ini berguna
karena dapat diterapkan ke composable mana pun.
Pengubah gambar
Semua perintah gambar dilakukan dengan pengubah gambar di Compose. Ada tiga pengubah gambar utama di Compose:
Pengubah dasar untuk gambar adalah drawWithContent
, tempat Anda dapat memutuskan
urutan gambar Composable dan perintah gambar yang dikeluarkan di dalam
pengubah. drawBehind
adalah wrapper praktis di sekitar drawWithContent
yang memiliki
urutan gambar yang ditetapkan di belakang konten composable. drawWithCache
memanggil onDrawBehind
atau onDrawWithContent
di dalamnya - dan menyediakan
mekanisme untuk menyimpan cache objek yang dibuat di dalamnya.
Modifier.drawWithContent
: Memilih urutan gambar
Modifier.drawWithContent
memungkinkan Anda
menjalankan operasi DrawScope
sebelum atau setelah konten
composable. Pastikan untuk memanggil drawContent
, lalu merender konten sebenarnya dari
composable. Dengan pengubah ini, Anda dapat menentukan urutan operasi,
jika ingin konten Anda digambar sebelum atau setelah operasi gambar
kustom.
Misalnya, jika ingin merender gradien radial di atas konten untuk membuat efek lubang kunci senter pada UI, Anda dapat melakukan hal berikut:
var pointerOffset by remember { mutableStateOf(Offset(0f, 0f)) } Column( modifier = Modifier .fillMaxSize() .pointerInput("dragging") { detectDragGestures { change, dragAmount -> pointerOffset += dragAmount } } .onSizeChanged { pointerOffset = Offset(it.width / 2f, it.height / 2f) } .drawWithContent { drawContent() // draws a fully black area with a small keyhole at pointerOffset that’ll show part of the UI. drawRect( Brush.radialGradient( listOf(Color.Transparent, Color.Black), center = pointerOffset, radius = 100.dp.toPx(), ) ) } ) { // Your composables here }
Modifier.drawBehind
: Menggambar di belakang composable
Modifier.drawBehind
memungkinkan Anda melakukan
operasi DrawScope
di belakang konten composable yang digambar di layar. Jika
melihat implementasi Canvas
, Anda mungkin menyadari bahwa
implementasi tersebut hanyalah wrapper praktis di sekitar Modifier.drawBehind
.
Untuk menggambar persegi panjang membulat di belakang Text
:
Text( "Hello Compose!", modifier = Modifier .drawBehind { drawRoundRect( Color(0xFFBBAAEE), cornerRadius = CornerRadius(10.dp.toPx()) ) } .padding(4.dp) )
Yang memberikan hasil berikut:
Modifier.drawWithCache
: Menggambar dan menyimpan cache objek gambar
Modifier.drawWithCache
memastikan objek
yang dibuat di dalamnya untuk disimpan dalam cache. Objek tersebut di-cache selama ukuran
area gambar sama, atau objek status apa pun yang dibaca tidak
berubah. Pengubah ini berguna untuk meningkatkan performa panggilan gambar karena
menghindari kebutuhan untuk mengalokasikan ulang objek (seperti: Brush, Shader, Path
, dll.)
yang dibuat pada gambar.
Atau, Anda juga dapat menyimpan cache objek menggunakan remember
, di luar
pengubah. Namun, hal ini tidak terus-menerus bisa dilakukan karena Anda tidak selalu memiliki akses
ke komposisi. Penggunaan drawWithCache
mungkin akan lebih efektif jika
objek hanya digunakan untuk menggambar.
Misalnya, jika Anda membuat Brush
untuk menggambar gradien di belakang Text
, penggunaan
drawWithCache
akan menyimpan cache objek Brush
hingga ukuran area gambar
berubah:
Text( "Hello Compose!", modifier = Modifier .drawWithCache { val brush = Brush.linearGradient( listOf( Color(0xFF9E82F0), Color(0xFF42A5F5) ) ) onDrawBehind { drawRoundRect( brush, cornerRadius = CornerRadius(10.dp.toPx()) ) } } )
Pengubah grafis
Modifier.graphicsLayer
: Menerapkan transformasi ke composable
Modifier.graphicsLayer
adalah pengubah yang membuat konten composable menggambar ke dalam lapisan gambar. Lapisan
menyediakan beberapa fungsi yang berbeda, seperti:
- Isolasi untuk petunjuk menggambarnya (mirip dengan
RenderNode
). Petunjuk menggambar yang diambil sebagai bagian dari lapisan dapat diterbitkan ulang secara efisien oleh pipeline rendering tanpa mengeksekusi ulang kode aplikasi. - Transformasi yang berlaku untuk semua petunjuk menggambar yang dimuat dalam lapisan.
- Rasterisasi untuk kemampuan komposisi. Jika lapisan dirasterisasi, petunjuk menggambar akan dijalankan dan output akan direkam menjadi buffering offscreen. Menggabungkan buffering seperti itu untuk frame berikutnya lebih cepat daripada mengeksekusi petunjuk individual, tetapi penggabungan akan berperilaku sebagai bitmap saat transformasi seperti penskalaan atau rotasi diterapkan.
Transformasi
Modifier.graphicsLayer
menyediakan isolasi untuk petunjuk menggambarnya; misalnya,
berbagai transformasi dapat diterapkan menggunakan Modifier.graphicsLayer
.
Ini dapat dianimasikan atau diubah tanpa perlu menjalankan ulang lambda
gambar.
Modifier.graphicsLayer
tidak mengubah ukuran atau penempatan yang dapat diukur dari composable Anda,
karena hanya memengaruhi fase menggambar. Ini berarti composable Anda
mungkin tumpang-tindih dengan composable lainnya jika akhirnya menggambar di luar batas tata letaknya.
Transformasi berikut dapat diterapkan dengan pengubah ini:
Skala - meningkatkan ukuran
scaleX
dan scaleY
akan memperbesar atau memperkecil konten dalam arah horizontal atau
vertikal. Nilai 1.0f
menunjukkan tidak ada perubahan skala, nilai
0.5f
berarti setengah dari dimensi.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.scaleX = 1.2f this.scaleY = 0.8f } )
Terjemahan
translationX
dan translationY
dapat diubah dengan graphicsLayer
,
dan translationX
akan memindahkan composable ke kiri atau kanan. Sementara translationY
akan memindahkan
composable ke atas atau ke bawah.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.translationX = 100.dp.toPx() this.translationY = 10.dp.toPx() } )
Rotasi
Setel rotationX
untuk memutar secara horizontal, rotationY
untuk memutar secara vertikal, dan
rotationZ
untuk memutar pada sumbu Z (rotasi standar). Nilai ini ditentukan
dalam derajat (0-360).
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.rotationX = 90f this.rotationY = 275f this.rotationZ = 180f } )
Origin
transformOrigin
dapat ditentukan. Kemudian, nilai ini digunakan sebagai titik tempat
transformasi terjadi. Semua contoh sejauh ini telah menggunakan
TransformOrigin.Center
, yang berada di (0.5f, 0.5f)
. Jika Anda menentukan originnya pada
(0f, 0f)
, transformasi akan dimulai dari sudut kiri
atas composable.
Jika mengubah origin dengan transformasi rotationZ
, Anda dapat melihat bahwa
item diputar di kiri atas composable:
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.transformOrigin = TransformOrigin(0f, 0f) this.rotationX = 90f this.rotationY = 275f this.rotationZ = 180f } )
Klip dan Bentuk
Bentuk menentukan garis batas tempat konten dipotong saat clip = true
. Dalam
contoh ini, kami menetapkan dua kotak untuk memiliki dua klip yang berbeda - satu menggunakan
variabel klip graphicsLayer
, dan satunya lagi menggunakan wrapper
Modifier.clip
yang praktis.
Column(modifier = Modifier.padding(16.dp)) { Box( modifier = Modifier .size(200.dp) .graphicsLayer { clip = true shape = CircleShape } .background(Color(0xFFF06292)) ) { Text( "Hello Compose", style = TextStyle(color = Color.Black, fontSize = 46.sp), modifier = Modifier.align(Alignment.Center) ) } Box( modifier = Modifier .size(200.dp) .clip(CircleShape) .background(Color(0xFF4DB6AC)) ) }
Konten kotak pertama (teks yang bertuliskan "Hello Compose") dipotong menjadi bentuk lingkaran:
Jika kemudian Anda menerapkan translationY
ke bagian atas lingkaran merah muda, Anda akan melihat bahwa batas
Composable masih sama, tetapi lingkaran digambar di balik lingkaran
yang ada di bawahnya (dan di luar batasnya).
Untuk menyesuaikan composable ke area yang digambar, Anda dapat menambahkan Modifier.clip(RectangleShape)
lain
di awal rantai pengubah. Konten tersebut
tetap berada di dalam batas asli.
Column(modifier = Modifier.padding(16.dp)) { Box( modifier = Modifier .clip(RectangleShape) .size(200.dp) .border(2.dp, Color.Black) .graphicsLayer { clip = true shape = CircleShape translationY = 50.dp.toPx() } .background(Color(0xFFF06292)) ) { Text( "Hello Compose", style = TextStyle(color = Color.Black, fontSize = 46.sp), modifier = Modifier.align(Alignment.Center) ) } Box( modifier = Modifier .size(200.dp) .clip(RoundedCornerShape(500.dp)) .background(Color(0xFF4DB6AC)) ) }
Alfa
Modifier.graphicsLayer
dapat digunakan untuk menyetel alpha
(opasitas) untuk seluruh
lapisan. 1.0f
sepenuhnya buram dan 0.0f
tidak terlihat.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "clock", modifier = Modifier .graphicsLayer { this.alpha = 0.5f } )
Strategi komposisi
Penggunaan alfa dan transparansi mungkin tidak semudah mengubah satu
nilai alfa. Selain mengubah alfa, ada juga opsi untuk menyetel
CompositingStrategy
di graphicsLayer
. CompositingStrategy
menentukan cara
konten composable disusun (disatukan) dengan konten
lain yang sudah digambar di layar.
Strategi yang berbeda adalah:
Auto (default)
Strategi komposisi ditentukan oleh parameter graphicsLayer
lainnya. Compose merender lapisan ke buffering offscreen jika alfa kurang dari
1,0f atau RenderEffect
disetel. Setiap kali alfa kurang dari 1f,
lapisan komposisi akan otomatis dibuat untuk merender konten, lalu menggambar
buffering offscreen ini ke tujuan dengan alfa yang sesuai. Menyetel
RenderEffect
atau overscroll akan selalu merender konten ke buffering
offscreen, apa pun setelan CompositingStrategy
.
Offscreen
Konten composable selalu dirasterisasi ke tekstur
atau bitmap offscreen sebelum dirender ke tujuan. Hal ini berguna untuk
menerapkan operasi BlendMode
untuk menyamarkan konten, dan untuk performa saat merender kumpulan petunjuk gambar yang kompleks.
Contoh penggunaan CompositingStrategy.Offscreen
adalah dengan BlendModes
. Perhatikan contoh di bawah,
misalnya Anda ingin menghapus bagian dari composable Image
dengan mengeluarkan perintah gambar yang
menggunakan BlendMode.Clear
. Jika compositingStrategy
tidak disetel ke
CompositingStrategy.Offscreen
, BlendMode
akan berinteraksi dengan semua konten
di bawahnya.
Image( painter = painterResource(id = R.drawable.dog), contentDescription = "Dog", contentScale = ContentScale.Crop, modifier = Modifier .size(120.dp) .aspectRatio(1f) .background( Brush.linearGradient( listOf( Color(0xFFC5E1A5), Color(0xFF80DEEA) ) ) ) .padding(8.dp) .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } .drawWithCache { val path = Path() path.addOval( Rect( topLeft = Offset.Zero, bottomRight = Offset(size.width, size.height) ) ) onDrawWithContent { clipPath(path) { // this draws the actual image - if you don't call drawContent, it wont // render anything this@onDrawWithContent.drawContent() } val dotSize = size.width / 8f // Clip a white border for the content drawCircle( Color.Black, radius = dotSize, center = Offset( x = size.width - dotSize, y = size.height - dotSize ), blendMode = BlendMode.Clear ) // draw the red circle indication drawCircle( Color(0xFFEF5350), radius = dotSize * 0.8f, center = Offset( x = size.width - dotSize, y = size.height - dotSize ) ) } } )
Jika CompositingStrategy
disetel ke Offscreen
, tekstur
offscreen akan dibuat untuk menjalankan perintah (hanya menerapkan BlendMode
ke
konten composable ini). Kemudian, akan merendernya di atas elemen yang sudah
dirender di layar, sehingga tidak memengaruhi konten yang sudah digambar.
Jika Anda tidak menggunakan CompositingStrategy.Offscreen
, hasil penerapan
BlendMode.Clear
akan menghapus semua piksel dalam tujuan, terlepas dari apa
yang telah disetel– membuat buffering rendering jendela (hitam) terlihat. Banyak
BlendModes
yang melibatkan alfa tidak akan berfungsi seperti yang diharapkan tanpa
buffering offscreen. Perhatikan lingkaran hitam di sekitar indikator lingkaran merah:
Untuk memahaminya sedikit lebih lanjut: jika aplikasi memiliki latar belakang jendela
yang transparan, dan Anda tidak menggunakan CompositingStrategy.Offscreen
,
BlendMode
akan berinteraksi dengan seluruh aplikasi. Tindakan ini akan menghapus semua piksel untuk menampilkan
aplikasi atau wallpaper di bawahnya, seperti dalam contoh berikut:
Perlu diperhatikan bahwa saat menggunakan CompositingStrategy.Offscreen
, tekstur
offscreen yang merupakan ukuran area gambar akan dibuat dan dirender kembali di
layar. Setiap perintah gambar yang dilakukan dengan strategi ini akan
dipotong secara default ke area ini. Cuplikan kode di bawah ini mengilustrasikan perbedaan saat
beralih untuk menggunakan tekstur offscreen:
@Composable fun CompositingStrategyExamples() { Column( modifier = Modifier .fillMaxSize() .wrapContentSize(Alignment.Center) ) { // Does not clip content even with a graphics layer usage here. By default, graphicsLayer // does not allocate + rasterize content into a separate layer but instead is used // for isolation. That is draw invalidations made outside of this graphicsLayer will not // re-record the drawing instructions in this composable as they have not changed Canvas( modifier = Modifier .graphicsLayer() .size(100.dp) // Note size of 100 dp here .border(2.dp, color = Color.Blue) ) { // ... and drawing a size of 200 dp here outside the bounds drawRect(color = Color.Magenta, size = Size(200.dp.toPx(), 200.dp.toPx())) } Spacer(modifier = Modifier.size(300.dp)) /* Clips content as alpha usage here creates an offscreen buffer to rasterize content into first then draws to the original destination */ Canvas( modifier = Modifier // force to an offscreen buffer .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) .size(100.dp) // Note size of 100 dp here .border(2.dp, color = Color.Blue) ) { /* ... and drawing a size of 200 dp. However, because of the CompositingStrategy.Offscreen usage above, the content gets clipped */ drawRect(color = Color.Red, size = Size(200.dp.toPx(), 200.dp.toPx())) } } }
ModulateAlpha
Strategi komposisi ini memodulasi alfa untuk setiap petunjuk
menggambar yang direkam dalam graphicsLayer
. Tindakan ini tidak akan membuat
buffering offscreen untuk alfa di bawah 1,0f kecuali jika RenderEffect
disetel, sehingga dapat
lebih efisien untuk rendering alfa. Namun, hal ini dapat memberikan hasil yang berbeda
untuk konten yang tumpang-tindih. Untuk kasus penggunaan jika sebelumnya diketahui bahwa konten
tidak tumpang-tindih, hal ini dapat memberikan performa yang lebih baik daripada
CompositingStrategy.Auto
dengan nilai alfa kurang dari 1.
Contoh lain dari strategi komposisi yang berbeda adalah di bawah - menerapkan alfa
yang berbeda untuk berbagai bagian composable, dan menerapkan strategi
Modulate
:
@Preview @Composable fun CompositingStrategy_ModulateAlpha() { Column( modifier = Modifier .fillMaxSize() .padding(32.dp) ) { // Base drawing, no alpha applied Canvas( modifier = Modifier.size(200.dp) ) { drawSquares() } Spacer(modifier = Modifier.size(36.dp)) // Alpha 0.5f applied to whole composable Canvas( modifier = Modifier .size(200.dp) .graphicsLayer { alpha = 0.5f } ) { drawSquares() } Spacer(modifier = Modifier.size(36.dp)) // 0.75f alpha applied to each draw call when using ModulateAlpha Canvas( modifier = Modifier .size(200.dp) .graphicsLayer { compositingStrategy = CompositingStrategy.ModulateAlpha alpha = 0.75f } ) { drawSquares() } } } private fun DrawScope.drawSquares() { val size = Size(100.dp.toPx(), 100.dp.toPx()) drawRect(color = Red, size = size) drawRect( color = Purple, size = size, topLeft = Offset(size.width / 4f, size.height / 4f) ) drawRect( color = Yellow, size = size, topLeft = Offset(size.width / 4f * 2f, size.height / 4f * 2f) ) } val Purple = Color(0xFF7E57C2) val Yellow = Color(0xFFFFCA28) val Red = Color(0xFFEF5350)
Menulis konten composable ke bitmap
Kasus penggunaan umum adalah membuat Bitmap
dari composable. Untuk menyalin
konten composable ke Bitmap
, buat GraphicsLayer
menggunakan
rememberGraphicsLayer()
.
Alihkan perintah gambar ke lapisan baru menggunakan drawWithContent()
dan
graphicsLayer.record{}
. Kemudian, gambar lapisan di kanvas yang terlihat menggunakan
drawLayer
:
val coroutineScope = rememberCoroutineScope() val graphicsLayer = rememberGraphicsLayer() Box( modifier = Modifier .drawWithContent { // call record to capture the content in the graphics layer graphicsLayer.record { // draw the contents of the composable into the graphics layer this@drawWithContent.drawContent() } // draw the graphics layer on the visible canvas drawLayer(graphicsLayer) } .clickable { coroutineScope.launch { val bitmap = graphicsLayer.toImageBitmap() // do something with the newly acquired bitmap } } .background(Color.White) ) { Text("Hello Android", fontSize = 26.sp) }
Anda dapat menyimpan bitmap ke disk dan membagikannya. Untuk mengetahui detail selengkapnya, lihat cuplikan contoh lengkap. Pastikan untuk memeriksa izin akses di perangkat sebelum mencoba menyimpan ke disk.
Pengubah gambar kustom
Untuk membuat pengubah kustom Anda sendiri, implementasikan antarmuka DrawModifier
. Hal ini
memberi Anda akses ke ContentDrawScope
, yang sama dengan yang ditampilkan
saat menggunakan Modifier.drawWithContent()
. Kemudian, Anda dapat mengekstrak operasi gambar
umum ke pengubah gambar kustom untuk membersihkan kode dan menyediakan
wrapper yang praktis; misalnya, Modifier.background()
adalah
DrawModifier
yang praktis.
Misalnya, jika ingin mengimplementasikan Modifier
yang membalik
konten secara vertikal, Anda dapat membuatnya sebagai berikut:
class FlippedModifier : DrawModifier { override fun ContentDrawScope.draw() { scale(1f, -1f) { this@draw.drawContent() } } } fun Modifier.flipped() = this.then(FlippedModifier())
Kemudian, gunakan pengubah terbalik ini yang diterapkan di Text
:
Text( "Hello Compose!", modifier = Modifier .flipped() )
Referensi lainnya
Untuk contoh lainnya penggunaan graphicsLayer
dan gambar kustom, lihat
referensi berikut:
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Grafis di Compose
- Menyesuaikan gambar {:#customize-image}
- Kotlin untuk Jetpack Compose