Canvas bileşenine ek olarak Compose, özel içerik çizmeye yardımcı olan çeşitli kullanışlı grafik Modifiers içerir. Bu değiştiriciler, her composable'a
uygulanabildiği için kullanışlıdır.
Çizim değiştiriciler
Tüm çizim komutları, Compose'da bir çizim değiştiriciyle yapılır. Compose'da üç ana çizim değiştirici vardır:
Çizim için temel değiştirici drawWithContent'dir. Bu değiştiriciyle, Composable'ınızın çizim sırasını ve değiştirici içinde verilen çizim komutlarını belirleyebilirsiniz. drawBehind, çizim sırası composable'ın içeriğinin arkasına ayarlanmış olan drawWithContent için kullanışlı bir sarmalayıcıdır. drawWithCache,
onDrawBehind veya onDrawWithContent işlevini çağırır ve bunlarda oluşturulan nesneleri önbelleğe almak için bir mekanizma sağlar.
Modifier.drawWithContent: Çizim sırasını seçin
Modifier.drawWithContent, composable'ın içeriğinden önce veya sonra DrawScope işlemlerini yürütmenize olanak tanır. Ardından, composable'ın gerçek içeriğini oluşturmak için drawContent işlevini çağırın. Bu değiştiriciyle, içeriğinizin özel çizim işlemlerinizden önce mi yoksa sonra mı çizilmesini istediğinize bağlı olarak işlem sırasını belirleyebilirsiniz.
Örneğin, kullanıcı arayüzünde el feneri anahtar deliği efekti oluşturmak için içeriğinizin üzerine dairesel renk geçişi uygulamak istiyorsanız aşağıdakileri yapabilirsiniz:
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: Bir composable'ın arkasına çizim yapma
Modifier.drawBehind, ekranda çizilen composable içeriğin arkasında DrawScope işlemleri yapmanıza olanak tanır. Canvas uygulamasını incelediğinizde, bunun Modifier.drawBehind için kullanışlı bir sarmalayıcı olduğunu fark edebilirsiniz.
Text öğesinin arkasına köşeleri yuvarlatılmış bir dikdörtgen çizmek için:
Text( "Hello Compose!", modifier = Modifier .drawBehind { drawRoundRect( Color(0xFFBBAAEE), cornerRadius = CornerRadius(10.dp.toPx()) ) } .padding(4.dp) )
Bu da aşağıdaki sonucu verir:
Modifier.drawWithCache: Çizim ve önbelleğe alma çizim nesneleri
Modifier.drawWithCache, içinde oluşturulan nesneleri önbelleğe alır. Nesneler, çizim alanının boyutu aynı olduğu veya okunan durum nesneleri değişmediği sürece önbelleğe alınır. Bu değiştirici, çizim sırasında oluşturulan nesnelerin (ör. Brush, Shader, Path vb.) yeniden tahsis edilmesi ihtiyacını ortadan kaldırdığı için çizim çağrılarının performansını artırmak için kullanışlıdır.
Alternatif olarak, nesneleri değiştiricinin dışında remember kullanarak da önbelleğe alabilirsiniz. Ancak, besteye her zaman erişiminiz olmadığı için bu her zaman mümkün olmayabilir. Nesneler yalnızca çizim için kullanılıyorsa drawWithCache kullanmak daha iyi performans sağlayabilir.
Örneğin, bir Text öğesinin arkasına gradyan çizmek için Brush oluşturursanız drawWithCache kullanıldığında çizim alanının boyutu değişene kadar Brush nesnesi önbelleğe alınır:
Text( "Hello Compose!", modifier = Modifier .drawWithCache { val brush = Brush.linearGradient( listOf( Color(0xFF9E82F0), Color(0xFF42A5F5) ) ) onDrawBehind { drawRoundRect( brush, cornerRadius = CornerRadius(10.dp.toPx()) ) } } )
Grafik değiştiriciler
Modifier.graphicsLayer: Oluşturulabilir öğelere dönüşüm işlemleri uygulama
Modifier.graphicsLayer,
composable'ın içeriğinin bir çizim katmanına çizilmesini sağlayan bir değiştiricidir. Katmanlar, aşağıdaki gibi birkaç farklı işlev sunar:
- Çizim talimatları için izolasyon (
RenderNodeile benzer). Bir katmanın parçası olarak yakalanan çizim talimatları, uygulama kodunu yeniden yürütmeden oluşturma hattı tarafından verimli bir şekilde yeniden yayınlanabilir. - Bir katmanda bulunan tüm çizim talimatları için geçerli olan dönüşümler.
- Oluşturma özellikleri için pikselleştirme. Bir katman rasterleştirildiğinde çizim talimatları yürütülür ve çıkış, ekran dışı bir arabelleğe alınır. Sonraki kareler için böyle bir arabelleği birleştirmek, bağımsız talimatları yürütmekten daha hızlıdır ancak ölçeklendirme veya döndürme gibi dönüşümler uygulandığında bitmap gibi davranır.
Dönüşümler
Modifier.graphicsLayer, çizim talimatları için izolasyon sağlar. Örneğin, Modifier.graphicsLayer kullanılarak çeşitli dönüşümler uygulanabilir. Bunlar, çizim lambda'sını yeniden yürütmeye gerek kalmadan canlandırılabilir veya değiştirilebilir.
Modifier.graphicsLayer, yalnızca çizim aşamasını etkilediğinden, composable öğenizin ölçülen boyutunu veya yerleşimini değiştirmez. Bu, composable'ınızın düzen sınırlarının dışına çizilmesi durumunda diğerleriyle çakışabileceği anlamına gelir.
Bu değiştiriciyle aşağıdaki dönüşümler uygulanabilir:
Ölçeklendirme - boyutu artırma
scaleX ve scaleY, içeriği sırasıyla yatay veya dikey yönde büyütür ya da küçültür. 1.0f değeri, ölçekte değişiklik olmadığını gösterir. 0.5f değeri, boyutun yarısı anlamına gelir.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.scaleX = 1.2f this.scaleY = 0.8f } )
Çeviri
translationX ve translationY, graphicsLayer ile değiştirilebilir.
translationX, composable'ı sola veya sağa taşır. translationY, birleştirilebilir öğeyi yukarı veya aşağı taşır.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.translationX = 100.dp.toPx() this.translationY = 10.dp.toPx() } )
Döndürme
Yatay döndürme için rotationX, dikey döndürme için rotationY ve Z ekseninde döndürme (standart döndürme) için rotationZ değerini ayarlayın. Bu değer derece (0-360) cinsinden belirtilir.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.rotationX = 90f this.rotationY = 275f this.rotationZ = 180f } )
Kaynak
transformOrigin belirtilebilir. Ardından bu nokta, dönüşümlerin gerçekleştiği nokta olarak kullanılır. Şimdiye kadarki tüm örneklerde (0.5f, 0.5f) adresindeki TransformOrigin.Center kullanıldı. Başlangıç noktasını (0f, 0f) olarak belirtirseniz dönüşümler, composable'ın sol üst köşesinden başlar.
Başlangıç noktasını rotationZ dönüşümüyle değiştirirseniz öğenin, composable'ın sol üst kısmında döndüğünü görebilirsiniz:
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 ve Şekil
Şekil, clip = true olduğunda içeriğin kırpılacağı ana hattı belirtir. Bu örnekte, iki kutuyu iki farklı klip içerecek şekilde ayarladık. Bunlardan biri graphicsLayer klip değişkenini, diğeri ise kullanışlı sarmalayıcı Modifier.clip'yı kullanıyor.
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)) ) }
İlk kutunun içeriği ("Hello Compose" metni) daire şekline kırpılır:
Ardından üstteki pembe daireye translationY uygularsanız composable'ın sınırlarının hâlâ aynı olduğunu ancak dairenin alttaki dairenin altında (ve sınırlarının dışında) çizildiğini görürsünüz.
Oluşturulabilir öğeyi çizildiği bölgeye kırpmak için değiştirici zincirinin başına başka bir Modifier.clip(RectangleShape) ekleyebilirsiniz. Böylece içerik, orijinal sınırların içinde kalır.
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, katmanın tamamı için alpha (opaklık) ayarlamak amacıyla kullanılabilir. 1.0f tamamen opak, 0.0f ise görünmezdir.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "clock", modifier = Modifier .graphicsLayer { this.alpha = 0.5f } )
Birleştirme stratejisi
Alfa ve şeffaflıkla çalışmak her zaman tek bir alfa değerini değiştirmek kadar basit değildir. Alfa değerini değiştirmenin yanı sıra graphicsLayer için CompositingStrategy ayarlama seçeneği de vardır. CompositingStrategy, composable'ın içeriğinin ekranda çizilmiş olan diğer içeriklerle nasıl birleştirileceğini (bir araya getirileceğini) belirler.
Farklı stratejiler şunlardır:
Otomatik (varsayılan)
Birleştirme stratejisi, diğer graphicsLayer parametreleri tarafından belirlenir. Alfa 1.0f'den küçükse veya bir RenderEffect ayarlanmışsa katmanı ekran dışı bir arabelleğe oluşturur. Alfa değeri 1f'den küçük olduğunda, içerikleri oluşturmak için otomatik olarak bir birleştirme katmanı oluşturulur ve ardından bu ekran dışı arabellek, ilgili alfa ile hedefe çizilir. RenderEffect veya aşırı kaydırma ayarlandığında, ayarlanan CompositingStrategy değerinden bağımsız olarak içerik her zaman ekran dışı bir arabelleğe aktarılır.
Ekran dışında
Birleştirilebilir öğenin içeriği, hedefe oluşturulmadan önce her zaman ekran dışı bir dokuya veya bit eşleme olarak rasterleştirilir. Bu, maske içeriğine BlendMode işlemleri uygulamak ve karmaşık çizim talimatı gruplarını oluştururken performans açısından kullanışlıdır.
CompositingStrategy.Offscreen, BlendModes ile birlikte kullanılabilir.
Aşağıdaki örnekte, BlendMode.Clear kullanan bir çizim komutu vererek Image composable'ının parçalarını kaldırmak istediğinizi varsayalım. compositingStrategy özelliğini CompositingStrategy.Offscreen olarak ayarlamazsanız BlendMode, tüm arka plan içeriğiyle etkileşim kurar.
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 ) ) } } )
CompositingStrategy, Offscreen olarak ayarlandığında komutların yürütüleceği ekran dışı bir doku oluşturulur (BlendMode yalnızca bu composable'ın içeriğine uygulanır). Ardından, ekranda oluşturulmuş olan içeriğin üzerine oluşturur ve önceden çizilmiş içeriği etkilemez.
CompositingStrategy.Offscreen kullanmadıysanız BlendMode.Clear uygulandığında, önceden ayarlanmış olanlardan bağımsız olarak hedefteki tüm pikseller temizlenir ve pencerenin oluşturma arabelleği (siyah) görünür kalır. Alfa içeren BlendModes işlemlerinin çoğu, ekran dışı arabellek olmadan beklendiği gibi çalışmaz. Kırmızı daire göstergesinin etrafındaki siyah halkaya dikkat edin:
Bu konuyu biraz daha ayrıntılı inceleyelim: Uygulamanın yarı saydam bir pencere arka planı varsa ve CompositingStrategy.Offscreen kullanmadıysanız BlendMode, uygulamanın tamamıyla etkileşim kurar. Alttaki uygulamayı veya duvar kağıdını göstermek için tüm pikselleri temizler. Bu örnekte olduğu gibi:
CompositingStrategy.Offscreen kullanılırken çizim alanı boyutunda bir ekran dışı doku oluşturulur ve ekrana geri aktarılır. Bu stratejiyle yapılan tüm çizim komutları varsayılan olarak bu bölgeye kırpılır. Aşağıdaki kod snippet'i, ekran dışı dokuları kullanmaya geçiş yaparken ortaya çıkan farklılıkları göstermektedir:
@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
Bu kompozisyon stratejisi, graphicsLayer içinde kaydedilen çizim talimatlarının her biri için alfayı modüle eder. RenderEffect ayarlanmadığı sürece 1, 0f'den küçük alfa için ekran dışı arabellek oluşturmaz.Bu nedenle alfa oluşturma için daha verimli olabilir. Ancak çakışan içerikler için farklı sonuçlar verebilir. İçeriğin çakışmadığı önceden bilinen kullanım alanlarında bu, 1'den küçük alfa değerleriyle CompositingStrategy.Auto değerinden daha iyi performans sağlayabilir.
Aşağıdaki örnekte, farklı birleştirme stratejileri gösterilmektedir. Birleştirilebilir öğelerin farklı bölümlerine farklı alfalar uygulanır ve Modulate stratejisi kullanılır:
@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) ) } @Preview @Composable fun ModifierGraphicsLayerOutsets() { Column( modifier = Modifier .background(Color(0xFF0A0E14)) .padding(48.dp), verticalArrangement = Arrangement.spacedBy(48.dp), horizontalAlignment = Alignment.CenterHorizontally ) { val pillShape = RoundedCornerShape(percent = 50) // Without outsets: alpha = 0.5f creates an offscreen buffer that clips the 24.dp glow Box( modifier = Modifier .size( width = 140.dp, height = 56.dp ) .graphicsLayer( alpha = 0.5f, clip = false ) .dropShadow( shape = pillShape, shadow = Shadow(radius = 24.dp, color = Color.Cyan) ) .background(color = Color(0xFF141824), shape = pillShape), contentAlignment = Alignment.Center ) { Text( text = "✨ Glow", color = Color.White, fontSize = 18.sp ) } Box( modifier = Modifier .size( width = 140.dp, height = 56.dp ) .graphicsLayer( alpha = 0.5f, outsets = LayerOutsets(24.dp), clip = false ) .dropShadow( shape = pillShape, shadow = Shadow(radius = 24.dp, color = Color.Cyan) ) .background(color = Color(0xFF141824), shape = pillShape), contentAlignment = Alignment.Center ) { Text( text = "✨ Glow", color = Color.White, fontSize = 18.sp ) } } } val Purple = Color(0xFF7E57C2) val Yellow = Color(0xFFFFCA28) val Red = Color(0xFFEF5350)
Katman çıkıntıları
Bir composable, ekran dışı bir arabelleğe çizildiğinde (örneğin, alpha, 1.0f değerinden küçük olduğunda, bir RenderEffect ayarlandığında veya CompositingStrategy.Offscreen kullanıldığında) ekran dışı doku, varsayılan olarak composable'ın ölçülen düzen sınırlarına ayarlanır. Katmanın içinde, composable'ın düzen sınırlarının dışına taşan tüm çizimler veya gölgeler (ör. Modifier.dropShadow ile çizilen dış ışık), ekran dışı arabelleğin sınırında dolaylı olarak kırpılır.
Modifier.padding() eklemek (bitişik kullanıcı arayüzü öğelerinin düzen ölçümünü değiştirir) yerine Modifier.graphicsLayer öğesinin outsets parametresine LayerOutsets değerini iletin. Bu, birleştirilebilir öğenin ölçülen düzen boyutunu değiştirmeden ekran dışı arabelleğin her iki taraftaki görsel sınırlarını genişletir. LayerOutsets, katmanın klip şeklini veya gölge sınırlarını genişletmez. Bunlar, composable'ın orijinal ölçülen boyutuna göre kalır (bu nedenle, orijinal sınırlara kırpılmayı önlemek için graphicsLayer üzerinde clip = false değerini koruyun):
Box( modifier = Modifier .size( width = 140.dp, height = 56.dp ) .graphicsLayer( alpha = 0.5f, outsets = LayerOutsets(24.dp), clip = false ) .dropShadow( shape = pillShape, shadow = Shadow(radius = 24.dp, color = Color.Cyan) ) .background(color = Color(0xFF141824), shape = pillShape), contentAlignment = Alignment.Center ) { Text( text = "✨ Glow", color = Color.White, fontSize = 18.sp ) }
LayerOutsets olmadan (üstte, composable'ın dikdörtgen düzen sınırlarında kırpılmış) ve LayerOutsets(24.dp) ile (altta, kırpılmamış) ekran dışı katman dış parıltısıBir composable'ın içeriğini bit eşleme dosyasına yazma
Yaygın kullanım alanlarından biri, composable'dan Bitmap oluşturmaktır. Birleştirilebilir öğenizin içeriğini bir Bitmap öğesine kopyalamak için rememberGraphicsLayer() kullanarak bir GraphicsLayer oluşturun.
drawWithContent() ve graphicsLayer.record{} kullanarak çizim komutlarını yeni katmana yönlendirin. Ardından, görünür tuvalde katmanı çizmek için şunları kullanın
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) }
Bit eşlemi diske kaydedip paylaşabilirsiniz. Daha fazla bilgi için tam örnek snippet'e bakın. Diske kaydetmeye çalışmadan önce cihaz izinlerini kontrol ettiğinizden emin olun.
Özel çizim değiştirici
Kendi özel değiştiricinizi oluşturmak için DrawModifier arayüzünü uygulayın. Bu sayede, Modifier.drawWithContent() kullanırken gösterilenle aynı olan bir ContentDrawScope öğesine erişebilirsiniz. Ardından, kodu temizlemek ve kullanışlı sarmalayıcılar sağlamak için yaygın çizim işlemlerini özel çizim değiştiricilere ayırabilirsiniz. Örneğin, Modifier.background() kullanışlı bir DrawModifier'dir.
Örneğin, içeriği
dikey olarak çeviren bir Modifier uygulamak istiyorsanız aşağıdaki gibi bir oluşturabilirsiniz:
class FlippedModifier : DrawModifier { override fun ContentDrawScope.draw() { scale(1f, -1f) { this@draw.drawContent() } } } fun Modifier.flipped() = this.then(FlippedModifier())
Ardından, Text için uygulanan bu ters çevrilmiş değiştiriciyi kullanın:
Text( "Hello Compose!", modifier = Modifier .flipped() )
Ek kaynaklar
graphicsLayer ve özel çizim kullanan daha fazla örnek için aşağıdaki kaynaklara göz atın:
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir.
- Compose'da grafikler
- Resim özelleştirme {:#customize-image}
- Jetpack Compose için Kotlin