Oluşturma'da, Canvas
bileşenine ek olarak özel içerik çizmenize yardımcı olacak çeşitli faydalı grafiklerModifiers
bulunur. Bu değiştiriciler, herhangi bir kompozisyona uygulanabildikleri için kullanışlıdır.
Çizim değiştiricileri
Tüm çizim komutları, Oluştur'da bir çizim değiştiricisiyle yapılır. Oluşturma bölümünde üç ana çizim değiştirici vardır:
Çizim için temel değiştirici drawWithContent
'tür. Burada, Composable'ınızın çizim sırasına ve değiştiricide verilen çizim komutlarına karar verebilirsiniz. drawBehind
, drawWithContent
etrafında uygun bir sarmalayıcıdır ve çizim sırasını, kompozisyonun içeriğinin arkasına ayarlanmıştır. drawWithCache
, içinde onDrawBehind
veya onDrawWithContent
'yi çağırır ve bu sınıflarda oluşturulan nesneleri önbelleğe alma mekanizması sağlar.
Modifier.drawWithContent
: Çizim sırasını seçin
Modifier.drawWithContent
, DrawScope
işlemlerini, birleştirilebilir öğenin içeriğinden önce veya sonra yürütmenize olanak tanır. Daha sonra, derlenebilir öğenin gerçek içeriğini oluşturmak için drawContent
işlevini çağırdığınızdan emin olun. Bu değiştiriciyi kullanarak, içeriğinizin özel çizim işlemlerinizden önce mi yoksa sonra mı çizileceğine karar verebilirsiniz.
Örneğin, kullanıcı arayüzünde el feneri anahtar deliği efekti oluşturmak için içeriğinizin üzerine radyal degrade 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 bileşenin arkasına çizim yapma
Modifier.drawBehind
, ekranda çizilen derlenebilir içeriğin arkasında DrawScope
işlemleri gerçekleştirmenize olanak tanır. Canvas
işlevini incelerseniz bunun Modifier.drawBehind
işlevinin etrafına yerleştirilmiş kullanışlı bir sarmalayıcı olduğunu fark edebilirsiniz.
Text
öğesinin arkasına 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 işlem 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 (ör. Brush, Shader, Path
vb.) yeniden atanması gerekmediğinden çizim çağrılarının performansını iyileştirmek için yararlıdır.
Alternatif olarak, değiştiricinin dışında remember
kullanarak da nesneleri önbelleğe alabilirsiniz. Ancak kompozisyona her zaman erişiminiz olmadığı için bu her zaman mümkün değildir. Nesneler yalnızca çizim için kullanılıyorsa drawWithCache
kullanmak daha yüksek performans sağlayabilir.
Örneğin, bir Text
'un arkasında degrade çizmek için bir Brush
oluşturursanız drawWithCache
, çizim alanının boyutu değişene kadar Brush
nesnesini önbelleğe alı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
: Bileşenlere dönüşümler uygulama
Modifier.graphicsLayer
, birleşik çizimin içeriğini çizim katmanı haline getiren bir değiştiricidir. Katmanlar aşağıdakiler gibi birkaç farklı işlev sağlar:
- Çizim talimatları için yalıtım (
RenderNode
'a benzer). Bir katmanın parçası olarak yakalanan çizim talimatları, uygulama kodu yeniden yürütülmeden oluşturma hattı tarafından verimli bir şekilde yeniden yayınlanabilir. - Bir katmandaki tüm çizim talimatları için geçerli olan dönüşümler.
- Beste özellikleri için rasterleştirme. Bir katman rasterleştirildiğinde çizim talimatları yürütülür ve çıkış, ekran dışı bir arabelleğe yakalanır. Sonraki kareler için bu tür bir arabelleğin birleştirilmesi, talimatları tek tek yürütmekten daha hızlıdır ancak ölçeklendirme veya döndürme gibi dönüştürme işlemleri uygulandığında bir bitmap gibi 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 lambda'sını yeniden yürütmeye gerek kalmadan animasyonlu hale getirilebilir veya değiştirilebilir.
Modifier.graphicsLayer
, yalnızca çizim aşamasını etkilediği için birleştirilebilir öğenizin ölçülen boyutunu veya yerleşimini değiştirmez. Bu, kompozisyonunuzun, düzen sınırlarının dışında çizilirse diğer öğelerle örtüşebileceği anlamına gelir.
Bu değiştirici ile aşağıdaki dönüşümler uygulanabilir:
Ölçek: Boyutu artırın
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 ise boyutun yarısını ifade eder.
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
, bileşiği sola veya sağa hareket ettirir. translationY
, bileşiği 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
rotationX
yatay, rotationY
dikey ve rotationZ
Z ekseninde (standart döndürme) döndürmek için ayarlanır. Bu değer derece cinsinden (0-360) belirtilir.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.rotationX = 90f this.rotationY = 275f this.rotationZ = 180f } )
Köken
Bir transformOrigin
belirtilebilir. Ardından, 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ılmıştır. (0f, 0f)
noktasında orijini belirtirseniz dönüşümler, birleştirilebilir öğenin sol üst köşesinden başlar.
Orijin noktasını rotationZ
dönüşümü ile değiştirirseniz öğenin, bileşimin 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ıldığı ana hattı belirtir. Bu örnekte, iki kutuyu iki farklı klip içerecek şekilde ayarladık. Biri graphicsLayer
klip değişkenini, diğeri ise kullanışlı sarmalayıcıyı Modifier.clip
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 ("Merhaba Oluştur" yazan metin) daire şekline kırpılır:
Ardından üstteki pembe daireye bir translationY
uygularsanız bileşimli öğenin sınırları aynı kalır ancak daire alttaki dairenin altında (ve sınırları dışında) çizilir.
Kompoziti çizildiği bölgeye kırpmak için değiştirici zincirinin başına başka bir Modifier.clip(RectangleShape)
ekleyebilirsiniz. İç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 bir alpha
(opaklık) ayarlamak için 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, tek bir alfa değerini değiştirmek kadar basit olmayabilir. Alfa değerini değiştirmenin yanı sıra graphicsLayer
üzerinde CompositingStrategy
ayarlama seçeneği de vardır. CompositingStrategy
, kompozisyonun içeriğinin ekranda önceden çizilmiş diğer içerikle nasıl birleştirileceğini (bir araya getirileceğini) belirler.
Farklı stratejiler şunlardır:
Otomatik (varsayılan)
Oluşturma stratejisi, graphicsLayer
parametrelerinin geri kalanı tarafından belirlenir. Alfa 1,0f'tan küçükse veya bir RenderEffect
ayarlanmışsa katmanı ekran dışı bir arabelleğe oluşturur. Alfa 1'den azsa içeriği oluşturmak ve ardından bu ekran dışı arabelleği hedefe ilgili alfa ile çizmek için otomatik olarak bir birleştirme katmanı oluşturulur. RenderEffect
veya aşırı kaydırma ayarı, CompositingStrategy
ayarından bağımsız olarak içeriği her zaman ekran dışı bir arabelleğe oluşturur.
Ekran dışında
Birleştirilebilir öğenin içeriği, hedefte oluşturulmadan önce her zaman ekran dışı bir dokuya veya bitmap'e rasterleştirilir. Bu, içeriği maskelemek için BlendMode
işlemlerini uygulamak ve karmaşık çizim talimatlarını oluşturma performansı için kullanışlıdır.
CompositingStrategy.Offscreen
türü, BlendModes
ile birlikte kullanılabilir. Aşağıdaki örneğe göz atalım. BlendMode.Clear
kullanan bir çizim komutu yayınlayarak Image
bileşeninin bazı kısımlarını kaldırmak istediğinizi varsayalım. compositingStrategy
'ü CompositingStrategy.Offscreen
olarak ayarlamazsanız BlendMode
, altındaki tüm içeriklerle 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ı yürütmek için ekran dışı bir doku oluşturur (BlendMode
yalnızca bu bileşiğin içeriğine uygulanır). Ardından, ekranda zaten oluşturulmuş olan içeriğin üzerine oluşturulur ve önceden çizilmiş içeriği etkilemez.
CompositingStrategy.Offscreen
kullanmadıysanız BlendMode.Clear
uygulamanın sonuçları, önceden ayarlanmış olandan bağımsız olarak hedefteki tüm pikselleri temizler ve pencerenin oluşturma arabelleğinin (siyah) görünür kalmasını sağlar. Alfa içeren BlendModes
öğelerinin çoğu, ekran dışı bir tampon olmadan beklendiği gibi çalışmaz. Kırmızı daire göstergesinin etrafındaki siyah halkayı not edin:
Bunu biraz daha açıklamak gerekirse: Uygulamanın şeffaf bir pencere arka planı varsa ve CompositingStrategy.Offscreen
'ü kullanmadıysanız BlendMode
, uygulamanın tamamıyla etkileşim kurar. Bu örnekte gösterildiği gibi, uygulamayı veya altındaki duvar kağıdını göstermek için tüm pikselleri temizler:
CompositingStrategy.Offscreen
kullanıldığında, çizim alanının boyutunda bir ekran dışı doku oluşturulup ekranda yeniden oluşturulduğunu belirtmek gerekir. Bu stratejiyle yapılan tüm çizim komutları varsayılan olarak bu bölgeye kırpılır. Aşağıdaki kod snippet'inde, ekran dışı dokulara geçişle ilgili 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 kompozisyon stratejisi, graphicsLayer
içinde kaydedilen her çizim talimatının alfa değerini modüle eder. RenderEffect
ayarlanmadığı sürece 1,0f'nin altındaki alfa için ekran dışı bir arabellek oluşturmaz.Bu nedenle, alfa oluşturma için daha verimli olabilir. Ancak çakışan içerikler için farklı sonuçlar sağlayabilir. İçeriğin çakışmadığından önceden bilinen kullanım alanlarında, alfa değerleri 1'den küçük olan CompositingStrategy.Auto
'ten daha iyi performans sağlayabilir.
Farklı kompozisyon stratejilerine örnek olarak aşağıdakileri verebiliriz: Birleştirilebilir öğelerin farklı bölümlerine farklı alfa değerleri uygulama ve Modulate
stratejisi uygulama:
@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)
Bir bileşiğin içeriğini bitmap'e yazma
Bu işlevin yaygın bir kullanım alanı, bir bileşenden Bitmap
oluşturmaktır. Kompozitinizin içeriğini bir Bitmap
'e kopyalamak için rememberGraphicsLayer()
kullanarak bir GraphicsLayer
oluşturun.
drawWithContent()
ve graphicsLayer.record{}
tuşlarını kullanarak çizim komutlarını yeni katmana yönlendirin. Ardından, drawLayer
seçeneğini kullanarak katmanı görünür kanvasta ç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şlemesini diske kaydedip paylaşabilirsiniz. Daha fazla bilgi için tam örnek snippet'ine 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ırken gösterilenle aynı olan bir ContentDrawScope
'ye 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 ayıklayabilirsiniz. Örneğin, Modifier.background()
kullanışlı bir DrawModifier
'dur.
Örneğin, içeriği dikey olarak çeviren bir Modifier
uygulamak istiyorsanız aşağıdaki gibi bir Modifier
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 ters değiştiriciyi kullanın:
Text( "Hello Compose!", modifier = Modifier .flipped() )
Ek kaynaklar
graphicsLayer
ve özel çizim kullanılan 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
- Oluşturma'daki grafikler
- Resim özelleştirme {:#customize-image}
- Jetpack Compose için Kotlin