Canvas
composable'ın yanı sıra Compose'da da özel içerik çizmeye yardımcı olan birkaç faydalı grafik
Modifiers
bulunur. Bu değiştiriciler tüm composable'lara
uygulanabileceğinden yararlıdır.
Çizim değiştiricileri
Tüm çizim komutları, Oluştur'da bir çizim değiştiriciyle yapılır. Oluşturma'da üç ana çizim değiştiricisi vardır:
Çizim için temel değiştirici olan drawWithContent
. Burada, Composable'ın çizim sırasını ve düzenleyicide verilen çizim komutlarını belirleyebilirsiniz. drawBehind
, çizim sırası composable'ın içeriğinin arkasına ayarlanmış drawWithContent
için uygun bir sarmalayıcıdır. drawWithCache
, içindeki onDrawBehind
veya onDrawWithContent
değerlerini çağırır ve bunlarda oluşturulan nesneleri önbelleğe alma mekanizması sağlar.
Modifier.drawWithContent
: Çizim sırasını seç
Modifier.drawWithContent
, composable'ın içeriğinden önce veya sonra DrawScope
işlemlerini yürütmenizi sağlar. Daha sonra composable'ın gerçek içeriğini oluşturmak için drawContent
yöntemini çağırdığınızdan emin olun. İçeriğinizin özel çizim işlemlerinizden önce veya sonra çizilmesini istiyorsanız bu değiştiriciyi kullanarak işlemlerin sırasına karar verebilirsiniz.
Örneğin, kullanıcı arayüzünde bir el feneri anahtar deliği efekti oluşturmak için içeriğinizin üzerinde dairesel gradyan oluşturmak isterseniz 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ında çizim yapma
Modifier.drawBehind
, ekranda çizilen composable içeriğin arkasında DrawScope
işlemi gerçekleştirmenizi sağlar. Canvas
uygulamasına göz atarsanız bunun Modifier.drawBehind
için kullanılan uygun bir sarmalayıcı olduğunu fark edebilirsiniz.
Text
öğesinin arkasında yuvarlak 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 nesnelerini çizme ve önbelleğe alma
Modifier.drawWithCache
, içinde oluşturulan nesneleri önbelleğe alır. Çizim alanının boyutu aynı olduğu veya okunan durum nesneleri değişmediği sürece nesneler önbelleğe alınır. Bu değiştirici, çizim sırasında oluşturulan nesnelerin (Brush, Shader, Path
vb.) yeniden tahsis edilmesi ihtiyacını ortadan kaldırdığından çizim çağrılarının performansını iyileştirmek için yararlıdır.
Alternatif olarak, nesneleri değiştiricinin dışında remember
kullanarak da önbelleğe alabilirsiniz. Ancak besteye her zaman erişiminiz olmadığından bu her zaman mümkün değildir. Nesneler yalnızca çizim için kullanılıyorsa drawWithCache
kullanmak daha etkili olabilir.
Örneğin, Text
arkasında bir gradyan çizmek için bir Brush
kullanırsanız drawWithCache
kullandığınızda, çizim alanının boyutu değişinceye 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
: composable'lara dönüşüm uygulama
Modifier.graphicsLayer
, composable çizim içeriğinin içeriğini çizim katmanına dönüştüren bir düzenleyicidir. Katman birkaç farklı işlev sağlar. Örneğin:
- Çizim talimatları için izolasyon (
RenderNode
'e benzer). Bir katmanın parçası olarak yakalanan çizim talimatları, uygulama kodu yeniden çalıştırılmadan oluşturma ardışık düzeni tarafından verimli bir şekilde yeniden yayınlanabilir. - Bir katmanda yer alan çizim talimatlarının tamamına uygulanan dönüşümlerdir.
- Kompozisyon özellikleri için pikselleştirme. Bir katman pikselleştirildiğinde, çizim talimatları yürütülür ve çıkış ekran dışı bir arabelleğe alınır. Sonraki kareler için bu tür bir arabelleğin birleştirilmesi, tek tek talimatların yürütülmesinden daha hızlıdır ancak ölçeklendirme veya döndürme gibi dönüşümler uygulandığında bit eşlem görevi görür.
Dönüşümler
Modifier.graphicsLayer
, çizim talimatları için yalıtım sağlar. Örneğin, Modifier.graphicsLayer
kullanılarak çeşitli dönüşümler uygulanabilir.
Bunlar, çizim lambda'nın yeniden çalıştırılmasına gerek kalmadan canlandırılabilir veya değiştirilebilir.
Modifier.graphicsLayer
, yalnızca çizim aşamasını etkilediğinden composable'ın ölçülen boyutunu veya yerleşimini değiştirmez. Diğer bir deyişle, composable'ınız düzen sınırlarının dışında çizim yapılırsa diğer öğelerle çakışabilir.
Bu değiştiriciyle aşağıdaki dönüşümler uygulanabilir:
Ölçek - boyutu büyütme
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
, composable'ı
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 olarak döndürmek için rotationX
, dikey olarak döndürmek için rotationY
ve Z ekseninde döndürmek için rotationZ
(standart döndürme) 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 } )
Kalkış noktası
Bir transformOrigin
belirtilebilir. Daha sonra dönüşümlerin gerçekleştiği nokta olarak kullanılır. Şimdiye kadar tüm örneklerde TransformOrigin.Center
kullanıldı. Bu, (0.5f, 0.5f)
. Kaynağı (0f, 0f)
konumunda belirtirseniz dönüşümler composable'ın sol üst köşesinden başlar.
Kaynağı rotationZ
dönüşümüyle değiştirirseniz öğenin composable'ın sol üst kısmı çevresinde 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 hatları belirtir. Bu örnekte, biri graphicsLayer
klip değişkenini ve diğeri uygun sarmalayıcıyı (Modifier.clip
) kullanan iki farklı klibe sahip iki kutuyu ayarladık.
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" yazan metin) daire şekline kesilir:
Daha sonra, üstteki pembe daireye bir translationY
uygularsanız Composable'ın sınırlarının hâlâ aynı olduğunu, ancak dairenin alt dairenin altına (ve sınırlarının dışına) çizildiğini görürsünüz.
composable'ı, ç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
, tüm katman için bir alpha
(opaklık) değeri ayarlamak amacıyla kullanılabilir. 1.0f
tamamen opak ve 0.0f
görünmez.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "clock", modifier = Modifier .graphicsLayer { this.alpha = 0.5f } )
Oluşturma stratejisi
Alfa ve şeffaflıkla çalışmak, tek bir alfa değerini değiştirmek kadar basit olmayabilir. Alfa sürümünü değiştirmenin yanı sıra, graphicsLayer
cihazında CompositingStrategy
ayarlama seçeneği de vardır. CompositingStrategy
, composable'ın içeriğinin ekranda çizilen diğer içerikle nasıl birleştirileceğini (bir araya getirileceğini) belirler.
Farklı stratejiler şunlardır:
Otomatik (varsayılan)
Birleştirme stratejisi, geri kalan graphicsLayer
parametreleri tarafından belirlenir. Alfa 1.0f'den düşükse veya bir RenderEffect
ayarlanmışsa katmanı ekran dışı arabelleğe oluşturur. Alfa 1f'den düşük olduğunda, içerikleri oluşturmak için otomatik olarak bir birleştirme katmanı oluşturulur ve daha sonra bu ekran dışı arabelleği karşılık gelen alfa ile hedefe çizin. RenderEffect
veya fazla kaydırma ayarlamak, CompositingStrategy
grubu ne olursa olsun içeriği her zaman ekran dışı arabelleğe oluşturur.
Ekran dışı
composable'ın içeriği, hedef için oluşturulmadan önce her zaman ekran dışı doku veya bit eşlem halinde pikselleştirilir. Bu, içeriği maskelemek için BlendMode
işlemleri uygulanırken ve karmaşık çizim talimatı gruplarını oluştururken performans açısından yararlıdır.
BlendModes
ile CompositingStrategy.Offscreen
kullanımına bir örnek verilebilir. Aşağıdaki örneğe göz atarken, BlendMode.Clear
kullanan bir çizim komutu yayınlayarak Image
composable'ının bazı bölümlerini kaldırmak istediğinizi varsayalım. compositingStrategy
öğesini CompositingStrategy.Offscreen
olarak ayarlamazsanız BlendMode
, altındaki tüm içeriklerle etkileşime geçer.
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şturur (BlendMode
öğesi yalnızca bu composable'ın içeriğine uygulanır). Daha sonra, çizilen içeriği etkilemeden bu resmi ekranda oluşturulmuş öğelerin üzerinde oluşturur.
CompositingStrategy.Offscreen
kullanmadıysanız BlendMode.Clear
uygulamasının sonuçları, daha önce yapılan ayardan bağımsız olarak hedefteki tüm pikselleri temizler. Böylece pencerenin oluşturma arabelleği (siyah) görünür durumda kalır. Alfa içeren BlendModes
öğelerinin çoğu, ekran dışı arabellek olmadan beklendiği gibi çalışmaz. Kırmızı daire göstergesinin etrafındaki siyah halkaya dikkat edin:
Bunu biraz daha ayrıntılı bir şekilde anlamak isteriz: Uygulamanın yarı saydam bir pencere arka planı varsa ve CompositingStrategy.Offscreen
öğesini kullanmadıysanız BlendMode
, uygulamanın tamamıyla etkileşimde bulunur. Aşağıdaki örnekte olduğu gibi, uygulamanın veya duvar kağıdının altında gösterilmesi için tüm pikseller temizlenir:
CompositingStrategy.Offscreen
kullanılırken, çizim alanının büyüklüğünde ekran dışı doku oluşturulur ve ekranda tekrar görüntülenir. Bu strateji ile 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şteki farkları 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 her çizim talimatının alfa sürümünü değiştirir. RenderEffect
ayarlanmadığı sürece 1.0f'nin altında alfa için ekran dışı arabellek oluşturmaz. Böylece alfa oluşturma için daha verimli olabilir. Ancak çakışan içerikler için
farklı sonuçlar sağlayabilir. İçeriğin çakışmadığının önceden bilindiği kullanım durumlarında bu, 1'den küçük alfa değerlerine sahip CompositingStrategy.Auto
uygulamasından daha iyi performans sağlayabilir.
Farklı beste stratejilerine başka bir örnek de şöyledir: composable'ların farklı bölümlerine farklı alfalar uygulama ve bir Modulate
stratejisi uygulama:
@Preview @Composable fun CompositingStratgey_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)
composable'ın içeriğini bit eşlem olarak yazma
Yaygın bir kullanım alanı, composable'dan Bitmap
oluşturmaktır. Beste dosyanızın içeriğini Bitmap
dosyasına kopyalamak için rememberGraphicsLayer()
ile bir GraphicsLayer
oluşturun.
drawWithContent()
ve graphicsLayer.record{}
kullanarak çizim komutlarını yeni katmana yönlendirin. Ardından, görünür tuvalde drawLayer
kullanarak katmanı çizin:
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 ayrıntılı bilgi için tam örnek snippet'e bakın. Diske kaydetmeye çalışmadan önce cihaz üzerinde izin olup olmadığını kontrol edin.
Özel çizim değiştirici
Kendi özel değiştiricinizi oluşturmak için DrawModifier
arayüzünü uygulayın. Bu şekilde bir ContentDrawScope
için erişim izni verilir. Bu, Modifier.drawWithContent()
kullanılırken gösterilen erişimle aynıdır. Daha sonra, kodu temizlemek ve kullanışlı sarmalayıcılar sağlamak için özel çizim değiştiricilere genel çizim işlemlerini ayıklayabilirsiniz. Örneğin, Modifier.background()
kullanışlı bir DrawModifier
'dır.
Örneğin, içeriği dikey olarak çeviren bir Modifier
uygulamak istiyorsanız aşağıdaki gibi oluşturabilirsiniz:
class FlippedModifier : DrawModifier { override fun ContentDrawScope.draw() { scale(1f, -1f) { this@draw.drawContent() } } } fun Modifier.flipped() = this.then(FlippedModifier())
Ardından, Text
üzerinde uygulanan bu çevrilmiş değiştiriciyi kullanın:
Text( "Hello Compose!", modifier = Modifier .flipped() )
Ek kaynaklar
graphicsLayer
ve özel çizim kullanımıyla ilgili daha fazla örnek için aşağıdaki kaynaklara göz atın:
Sizin için önerilenler
- Not: Bağlantı metni JavaScript kapalıyken görüntülenir
- Oluşturulan grafik
- Resmi özelleştirme {:#customize-image}
- Jetpack Compose için Kotlin