Compose, Canvas
composable'ın yanı sıra özel içerik çizmeye yardımcı olan birkaç kullanışlı grafik de
Modifiers
içerir. Bu değiştiriciler, her composable'a
uygulanabileceğinden faydalıdır.
Çizim değiştiricileri
Tüm çizim komutları, Oluşturma'da bir çizim değiştiriciyle yapılır. Oluşturma'da üç ana çizim değiştiricisi vardır:
Çizimin temel değiştiricisi drawWithContent
şeklindedir. Burada, Özelleştirilebilir'in çizim sırasına ve değiştirici içinde verilen çizim komutlarına karar verebilirsiniz. drawBehind
, çizim sırası composable'ın içeriğinin arkasına ayarlandığı drawWithContent
için kullanışlı bir sarmalayıcıdır. drawWithCache
, bunun içinde onDrawBehind
veya onDrawWithContent
yöntemini çağırır ve bunların içinde 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şlemleri yapmanıza olanak tanır. composable'ın gerçek içeriğini oluşturmak için drawContent
yöntemini çağırdığınızdan emin olun. Bu değiştiriciyle, içeriğinizin özel çizim işlemlerinizden önce veya sonra çizilmesini istiyorsanız işlemlerin sırasına karar verebilirsiniz.
Örneğin, kullanıcı arayüzünde el feneri anahtar deliği efekti oluşturmak için içeriğinizin üzerinde dairesel bir renk geçişi oluşturmak 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çeriklerin arkasında
DrawScope
işlemleri gerçekleştirmenizi sağlar. Canvas
uygulamasına göz atarsanız bunun yalnızca Modifier.drawBehind
etiketi için uygun bir sarmalayıcı olduğunu fark edebilirsiniz.
Text
öğesinin arkasına yuvarlak bir dikdörtgen çizmek için:
Text( "Hello Compose!", modifier = Modifier .drawBehind { drawRoundRect( Color(0xFFBBAAEE), cornerRadius = CornerRadius(10.dp.toPx()) ) } .padding(4.dp) )
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ığı için çizim çağrılarının performansını iyileştirmek açısından faydalıdır.
Alternatif olarak, değiştiricinin dışında, remember
öğesini kullanarak da nesneleri önbelleğe alabilirsiniz. Ancak besteye her zaman erişemediğiniz için bu her zaman mümkün olmaz. Nesneler yalnızca çizim için kullanılıyorsa drawWithCache
işlevinin kullanılması daha etkili olabilir.
Örneğin, bir Text
öğesinin arkasına bir renk geçişi ç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
: Dönüşüm işlemlerini composable'lara uygulama
Modifier.graphicsLayer
, composable çizim içeriğini bir çizim katmanına dönüştüren bir değiştiricidir. Katman, aşağıdakiler gibi birkaç farklı işlev sağlar:
- Çizim talimatları için izolasyon (
RenderNode
benzeri). Bir katmanın bir parçası olarak yakalanan çizim talimatları, oluşturma ardışık düzeni tarafından uygulama kodu yeniden yürütülmeden etkili bir şekilde yeniden verilebilir. - Bir katmanda yer alan tüm çizim talimatlarına uygulanan dönüşümler.
- Kompozisyon özellikleri için pikselleştirme. Bir katman pikselleştirildiğinde, çizim talimatları uygulanır ve çıkış, ekran dışı bir arabelleğe alınır. Böyle bir arabelleği sonraki kareler için birleştirmek, tek tek talimatları uygulamaktan daha hızlıdır ancak ölçeklendirme veya döndürme gibi dönüşümler uygulandığında bit eşlem olarak davranı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 lambdasını yeniden yürütmeye gerek kalmadan canlandırılabilir veya değiştirilebilir.
Modifier.graphicsLayer
, yalnızca çizim aşamasını etkilediği için composable'ınızın ölçülen boyutunu veya yerleşimini değiştirmez. Bu, composable'ınızın düzen sınırlarının dışında
çizim yapması halinde başkalarıyla çakışabileceği anlamına gelir.
Bu değiştiriciyle aşağıdaki dönüşümler uygulanabilir:
Ölçek - boyutu artır
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ı, 0.5f
değeri ise 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 döndürmek için rotationX
simgesini, dikey olarak döndürmek için rotationY
değerini ve Z ekseninde döndürmek için rotationZ
simgesini (standart döndürme) 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ı
transformOrigin
belirtilebilir. Daha sonra 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ı. Kaynağı (0f, 0f)
adresinde 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öşesinde 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
sırasında içeriğin klibinin ana hatlarını belirtir. Bu örnekte, biri graphicsLayer
klip değişkeni, diğeri de pratik sarmalayıcı Modifier.clip
olacak şekilde iki farklı klibe sahip iki kutu 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 ("Merhaba Oluştur" yazan metin) daire şekline kırpılır:
Daha sonra, üstteki pembe daireye bir translationY
değeri uygularsanız Özelleştirilebilir'in sınırlarının 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 göre klip oluşturmak için değiştirici zincirinin başına başka bir Modifier.clip(RectangleShape)
ekleyebilirsiniz. Bu durumda 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 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 } )1 bölümüne bakın.
CompositingStrategy
Birleştirme stratejisi
Alfa ve şeffaflıkla çalışmak, tek bir alfa değerini değiştirmek kadar kolay olmayabilir. Alfa sürümünü değiştirmenin yanı sıra graphicsLayer
cihazda CompositingStrategy
ayarlama seçeneği de vardır. CompositingStrategy
, composable'ın içeriğinin ekranda çizilmiş diğer içerikle nasıl birleştirileceğini (birleştirileceğ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 arabellekte oluşturur. Alfa değeri 1f'den düşük olduğunda, içeriğin oluşturulması için otomatik olarak bir birleştirme katmanı oluşturulur ve ardından bu ekran dışı arabelleği ilgili alfa sürümüyle hedefe çizeriz. RenderEffect
veya aşırı kaydırma ayarlamak, CompositingStrategy
grubundan bağımsız olarak içeriği her zaman ekran dışı bir arabellekte oluşturur.
Ekran dışı
composable'ın içeriği, hedef olarak oluşturulmadan önce her zaman ekran dışı bir dokuya veya bit eşlem olarak pikselleştirilir. Bu, BlendMode
işlemlerini içeriği maskelemek için uygulamak ve karmaşık çizim talimatı gruplarını oluştururken performansı artırmak açısından faydalıdır.
BlendModes
, CompositingStrategy.Offscreen
kullanımına örnek olarak verilebilir. Aşağıdaki örneğe göz atarak BlendMode.Clear
kullanan bir çizim komutu vererek Image
composable'ı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 girer.
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 bir ekran dışı doku oluşturur (BlendMode
öğesini yalnızca bu composable'ın içeriklerine uygular). Daha sonra, çizilmiş içeriği etkilemeden, resmi ekranda
oluşturulan içeriğin üzerinde oluşturur.
CompositingStrategy.Offscreen
kullanmadıysanız BlendMode.Clear
uygulanmanın sonuçları, önceden ayarlananlara bakılmaksızın hedefteki tüm pikselleri temizler ve pencerenin oluşturma arabelleğini (siyah) görünür bırakı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 iyi anlamak gerekirse: Uygulama yarı saydam bir pencere arka planına sahipse ve CompositingStrategy.Offscreen
özelliğini
kullanmadıysanız BlendMode
tüm uygulamayla etkileşim kurar. Aşağıdaki örnekte olduğu gibi, uygulamanın veya duvar kağıdının altında görünmesi için tüm pikseller temizlenir:
CompositingStrategy.Offscreen
kullanıldığında, çizim alanının boyutunda olan bir ekran dışı dokunun oluşturulduğunu ve tekrar ekranda oluşturulduğunu belirtmek gerekir. Bu stratejiyle yapılan çizim komutları varsayılan olarak bu bölgeye kopyalanır. Aşağıdaki kod snippet'inde, ekran dışı dokuları
kullanmaya geçişte görülen farklılıklar gösterilmektedir:
@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 beste stratejisi, graphicsLayer
içinde kaydedilen her çizim talimatı için alfa modunu değiştirir. RenderEffect
ayarlanmadığı sürece alfa sürümü için 1,0f'nin altında 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ığının önceden bilindiği kullanım alanlarında bu, alfa değerlerinin 1'den düşük olduğu CompositingStrategy.Auto
ürününden daha iyi performans sağlayabilir.
Farklı beste stratejilerine bir başka örnek aşağıda verilmiştir: 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)
Bir composable'ın içeriğini bit eşlem olarak yazma
Yaygın kullanım alanlarından biri, bir composable'dan Bitmap
oluşturmaktır. composable'ınızın içeriğini Bitmap
öğesine kopyalamak için rememberGraphicsLayer()
kullanarak bir GraphicsLayer
oluşturun.
Çizim komutlarını drawWithContent()
ve graphicsLayer.record{}
tuşlarını kullanarak yeni katmana yönlendirin. Ardından, drawLayer
komutunu kullanarak katmanı görünür tuvalde ç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 kaydedebilir ve paylaşabilirsiniz. Daha fazla bilgi için tam örnek snippet'e bakın. Diske kaydetmeyi denemeden önce cihazdaki izinleri kontrol edin.
Ö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ılırken gösterilenle aynı olan 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ştiricilerine
çıkarabilirsiniz. Örneğin, Modifier.background()
kullanımı kolaydır
DrawModifier
.
Örneğin, içeriği dikey olarak çeviren bir Modifier
uygulamak istiyorsanız aşağıdaki gibi bir sürüm oluşturabilirsiniz:
class FlippedModifier : DrawModifier { override fun ContentDrawScope.draw() { scale(1f, -1f) { this@draw.drawContent() } } } fun Modifier.flipped() = this.then(FlippedModifier())
Ardından, Text
tarihinde uygulanan ters çevrilmiş bu değiştiriciyi kullanın:
Text( "Hello Compose!", modifier = Modifier .flipped() )
Ek kaynaklar
graphicsLayer
ve özel çizimin kullanıldığı 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'daki grafikler
- Bir resmi özelleştirme {:#customize-image}
- Jetpack Compose için Kotlin