Compose, diğer çoğu kullanıcı arayüzü araç setinde olduğu gibi bir kareyi birkaç farklı aşamada oluşturur. Örneğin, Android View sisteminin üç ana aşaması vardır: ölçme, düzen ve çizim. Compose da çok benzerdir ancak başlangıçta kompozisyon adı verilen önemli bir ek aşama bulunur.
Compose belgelerinde, Compose'da düşünme ve Durum ve Jetpack Compose bölümlerinde kompozisyon açıklanmaktadır.
Karelerin üç aşaması
Oluşturma üç ana aşamadan oluşur:
- Kompozisyon: Gösterilecek ne kullanıcı arayüzü. Compose, composable işlevleri çalıştırır ve kullanıcı arayüzünüzün açıklamasını oluşturur.
- Düzen: Kullanıcı arayüzünün nereye yerleştirileceği. Bu aşama iki adımdan oluşur: ölçüm ve yerleştirme. Düzen öğeleri, düzen ağacındaki her düğüm için kendilerini ve alt öğeleri 2 boyutlu koordinatlarda ölçüp yerleştirir.
- Çizim: Nasıl oluşturulur? Kullanıcı arayüzü öğeleri genellikle bir cihaz ekranı olan tuval üzerine çizilir.

Bu aşamaların sırası genellikle aynıdır. Bu sayede veriler, kompozisyondan düzene, düzenden çizime doğru tek bir yönde akarak bir çerçeve oluşturur (tek yönlü veri akışı olarak da bilinir). BoxWithConstraints
, LazyColumn
ve LazyRow
, alt öğelerinin bileşimi üst öğenin düzen aşamasına bağlı olan önemli istisnalardır.
Kavramsal olarak bu aşamaların her biri her kare için gerçekleşir. Ancak performansı optimize etmek amacıyla Compose, bu aşamaların tümünde aynı girişlerden aynı sonuçları hesaplayacak işleri tekrarlamaktan kaçınır. Compose, önceki bir sonucu yeniden kullanabiliyorsa composable işlevini çalıştırmayı atlar ve Compose kullanıcı arayüzü, gerekmediği durumlarda ağacın tamamını yeniden düzenlemez veya yeniden çizmez. Compose, kullanıcı arayüzünü güncellemek için gereken minimum miktarda iş yapar. Compose, farklı aşamalardaki durum okumalarını izlediğinden bu optimizasyon mümkündür.
Aşamaları anlama
Bu bölümde, üç Compose aşamasının composable'lar için nasıl yürütüldüğü daha ayrıntılı olarak açıklanmaktadır.
Beste
Oluşturma aşamasında, Oluşturma çalışma zamanı composable işlevleri yürütür ve kullanıcı arayüzünüzü temsil eden bir ağaç yapısı oluşturur. Bu kullanıcı arayüzü ağacı, aşağıdaki videoda gösterildiği gibi sonraki aşamalar için gereken tüm bilgileri içeren düzen düğümlerinden oluşur:
Şekil 2. Birleştirme aşamasında oluşturulan, kullanıcı arayüzünüzü temsil eden ağaç.
Kod ve kullanıcı arayüzü ağacının bir alt bölümü aşağıdaki gibi görünür:

Bu örneklerde, koddaki her bir composable işlev, kullanıcı arayüzü ağacındaki tek bir düzen düğmesiyle eşlenir. Daha karmaşık örneklerde, composable işlevler mantık ve kontrol akışı içerebilir ve farklı durumlar verildiğinde farklı bir ağaç oluşturabilir.
Düzen
Compose, düzen aşamasında giriş olarak kompozisyon aşamasında oluşturulan kullanıcı arayüzü ağacını kullanır. Düzen düğümleri koleksiyonu, her düğümün 2D alandaki boyutu ve konumuna karar vermek için gereken tüm bilgileri içerir.
Şekil 4. Düzen aşamasında, kullanıcı arayüzü ağacındaki her düzen düğümünün ölçümü ve yerleşimi.
Düzen aşamasında, ağaç aşağıdaki üç adımlı algoritma kullanılarak geçilir:
- Çocukları ölçme: Bir düğüm, varsa alt düğümlerini ölçer.
- Kendi boyutuna karar verme: Bir düğüm, bu ölçümlere göre kendi boyutuna karar verir.
- Çocukları yerleştirme: Her alt düğüm, bir düğümün kendi konumuna göre yerleştirilir.
Bu aşamanın sonunda her düzen düğümünde şunlar bulunur:
- Atanmış bir genişlik ve yükseklik
- Çizilmesi gereken x, y koordinatı
Önceki bölümdeki kullanıcı arayüzü ağacını hatırlayın:
Bu ağaç için algoritma şu şekilde çalışır:
Row
, alt öğeleriImage
veColumn
için ölçüm yapar.Image
ölçülür. Çocuğu olmayan bu öğe, kendi boyutuna karar verir ve boyutuRow
'a bildirir.- Ardından
Column
ölçülür. Öncelikle kendi alt öğelerini (ikiText
composable) ölçer. - İlk
Text
ölçülür. Alt öğesi olmayan bu öğe, kendi boyutunu belirler ve boyutunuColumn
öğesine bildirir.- İkinci
Text
ölçülür. Alt öğesi olmadığı için kendi boyutuna karar verir ve bunuColumn
öğesine bildirir.
- İkinci
Column
, kendi boyutuna karar vermek için çocuğun ölçülerini kullanır. Maksimum alt öğe genişliğini ve alt öğelerinin yüksekliğinin toplamını kullanır.Column
, alt öğelerini kendisine göre yerleştirir ve bunları dikey olarak birbirinin altına koyar.Row
, kendi boyutuna karar vermek için çocuğun ölçülerini kullanır. Maksimum alt öğe yüksekliğini ve alt öğelerinin genişliklerinin toplamını kullanır. Ardından alt öğelerini yerleştirir.
Her düğümün yalnızca bir kez ziyaret edildiğini unutmayın. Compose çalışma zamanı, tüm düğümleri ölçmek ve yerleştirmek için kullanıcı arayüzü ağacında yalnızca bir geçiş gerektirir. Bu da performansı artırır. Ağaçtaki düğüm sayısı arttıkça ağaçta gezinmek için harcanan süre doğrusal olarak artar. Buna karşılık, her düğüm birden fazla kez ziyaret edilirse geçiş süresi eksponansiyel olarak artar.
Çizim
Çizim aşamasında ağaç, yukarıdan aşağıya doğru tekrar geçilir ve her düğüm sırayla kendini ekrana çizer.
5.şekil Çizim aşamasında, ekrandaki pikseller çizilir.
Önceki örneği kullanarak ağaç içeriği aşağıdaki şekilde çizilir:
Row
, arka plan rengi gibi sahip olabileceği tüm içerikleri çizer.Image
kendi kendini çizer.Column
kendi kendini çizer.- Birinci ve ikinci
Text
sırasıyla kendilerini çizer.
6.şekil Bir kullanıcı arayüzü ağacı ve bunun çizilmiş temsili.
Durum okuma işlemleri
Önceden listelenen aşamalardan birinde snapshot state
value
okuduğunuzda Oluştur, value
okurken ne yaptığını otomatik olarak izler. Bu izleme, durumun value
değiştiğinde Compose'un okuyucuyu yeniden yürütmesine olanak tanır ve Compose'da durum gözlemlenebilirliğinin temelini oluşturur.
Genellikle mutableStateOf()
kullanarak durum oluşturur ve ardından iki yoldan biriyle erişirsiniz: doğrudan value
özelliğine erişerek veya alternatif olarak bir Kotlin özelliği temsilcisi kullanarak. Bu konular hakkında daha fazla bilgiyi State in composables (Composable'larda durum) başlıklı makalede bulabilirsiniz. Bu kılavuzun amaçları doğrultusunda, "durum okuma" bu eşdeğer erişim yöntemlerinden birini ifade eder.
// State read without property delegate. val paddingState: MutableState<Dp> = remember { mutableStateOf(8.dp) } Text( text = "Hello", modifier = Modifier.padding(paddingState.value) )
// State read with property delegate. var padding: Dp by remember { mutableStateOf(8.dp) } Text( text = "Hello", modifier = Modifier.padding(padding) )
Özellik temsilcisinin arka planında, State'in value
değerine erişmek ve bu değeri güncellemek için"getter " ve"setter" işlevleri kullanılır. Bu getter ve setter işlevleri yalnızca özelliği değer olarak referans verdiğinizde çağrılır, oluşturulduğunda çağrılmaz. Bu nedenle, daha önce açıklanan iki yöntem eşdeğerdir.
Okuma durumu değiştiğinde yeniden yürütülebilen her kod bloğu bir yeniden başlatma kapsamıdır. Compose, durum value
değişikliklerini takip eder ve farklı aşamalarda kapsamları yeniden başlatır.
Aşamalı durum okuma işlemleri
Daha önce de belirtildiği gibi Compose'da üç ana aşama vardır ve Compose, her aşamada hangi durumun okunduğunu izler. Bu sayede Compose, kullanıcı arayüzünüzün etkilenen her öğesi için yalnızca iş yapması gereken belirli aşamaları bilgilendirebilir.
Aşağıdaki bölümlerde her aşama ve bu aşamalarda bir durum değeri okunduğunda ne olduğu açıklanmaktadır.
1. aşama: Kompozisyon
Bir @Composable
işlevi veya lambda bloğu içindeki durum okumaları, oluşturmayı ve muhtemelen sonraki aşamaları etkiler. Durumun value
değişmesi durumunda, yeniden oluşturucu, söz konusu durumun value
değerini okuyan tüm composable işlevlerin yeniden çalıştırılmasını planlar. Girişler değişmediyse çalışma zamanının, composable işlevlerin bazılarını veya tamamını atlamaya karar verebileceğini unutmayın. Daha fazla bilgi için Girişler değişmediyse atlama başlıklı makaleyi inceleyin.
Compose UI, kompozisyonun sonucuna bağlı olarak düzen ve çizim aşamalarını çalıştırır. İçerik aynı kalırsa ve boyut ile düzen değişmezse bu aşamalar atlanabilir.
var padding by remember { mutableStateOf(8.dp) } Text( text = "Hello", // The `padding` state is read in the composition phase // when the modifier is constructed. // Changes in `padding` will invoke recomposition. modifier = Modifier.padding(padding) )
2. aşama: Düzen
Düzen aşaması iki adımdan oluşur: ölçüm ve yerleştirme. Ölçüm adımı, Layout
composable'a iletilen ölçüm lambdasını, LayoutModifier
arayüzünün MeasureScope.measure
yöntemini ve diğerlerini çalıştırır.
Yerleştirme adımı, layout
işlevinin yerleştirme bloğunu, Modifier.offset { … }
işlevinin lambda bloğunu ve benzer işlevleri çalıştırır.
Bu adımların her birinde durum okuma işlemleri düzeni ve muhtemelen çizim aşamasını etkiler. Durum value
değiştiğinde Compose kullanıcı arayüzü, düzen aşamasını planlar. Boyut veya konum değiştiyse çizim aşamasını da çalıştırır.
var offsetX by remember { mutableStateOf(8.dp) } Text( text = "Hello", modifier = Modifier.offset { // The `offsetX` state is read in the placement step // of the layout phase when the offset is calculated. // Changes in `offsetX` restart the layout. IntOffset(offsetX.roundToPx(), 0) } )
3. aşama: Çizim
Çizim kodu sırasında durum okumaları, çizim aşamasını etkiler. Sık karşılaşılan örnekler arasında Canvas()
, Modifier.drawBehind
ve Modifier.drawWithContent
yer alır. Durum value
değiştiğinde Compose UI yalnızca çizim aşamasını çalıştırır.
var color by remember { mutableStateOf(Color.Red) } Canvas(modifier = modifier) { // The `color` state is read in the drawing phase // when the canvas is rendered. // Changes in `color` restart the drawing. drawRect(color) }
Durum okumalarını optimize etme
Compose, yerelleştirilmiş durum okuma izleme işlemi gerçekleştirdiğinden her durumu uygun bir aşamada okuyarak yapılan iş miktarını en aza indirebilirsiniz.
Aşağıdaki örneği inceleyin. Bu örnekte, son düzen konumunu kaydırmak için offset değiştiricisini kullanan bir Image()
var. Bu da kullanıcı kaydırdıkça paralaks efektiyle sonuçlanıyor.
Box { val listState = rememberLazyListState() Image( // ... // Non-optimal implementation! Modifier.offset( with(LocalDensity.current) { // State read of firstVisibleItemScrollOffset in composition (listState.firstVisibleItemScrollOffset / 2).toDp() } ) ) LazyColumn(state = listState) { // ... } }
Bu kod çalışıyor ancak optimum olmayan bir performansla sonuçlanıyor. Kod, yazıldığı şekliyle firstVisibleItemScrollOffset
durumunun value
değerini okur ve Modifier.offset(offset: Dp)
işlevine iletir. Kullanıcı kaydırdıkça firstVisibleItemScrollOffset
'nın value
değişir. Öğrendiğiniz gibi Compose, okuma kodunu (bu örnekte Box
içeriği) yeniden başlatabilmek (yeniden çağırabilmek) için tüm durum okumalarını izler.
Bu, composition (oluşturma) aşamasında bir durumu okuma örneğidir. Bu durum mutlaka kötü bir şey değildir ve aslında yeniden oluşturmanın temelini oluşturur. Bu sayede veri değişiklikleri yeni kullanıcı arayüzü oluşturabilir.
Önemli nokta: Bu örnek, her kaydırma etkinliği sonucunda tüm birleştirilebilir içeriğin yeniden değerlendirilmesi, ölçülmesi, düzenlenmesi ve son olarak çizilmesi nedeniyle ideal değildir. Gösterilen içerik değişmemiş olsa da yalnızca konumu değiştiği için her kaydırmada Oluşturma aşamasını tetikliyorsunuz. Durum okuma işlemini yalnızca düzen aşamasını yeniden tetikleyecek şekilde optimize edebilirsiniz.
Lambda ile telafi
Bir başka uzaklık değiştirici sürümü daha mevcuttur:
Modifier.offset(offset: Density.() -> IntOffset)
.
Bu sürüm, lambda parametresi alır. Sonuçtaki uzaklık, lambda bloğu tarafından döndürülür. Kodu kullanmak için güncelleyin:
Box { val listState = rememberLazyListState() Image( // ... Modifier.offset { // State read of firstVisibleItemScrollOffset in Layout IntOffset(x = 0, y = listState.firstVisibleItemScrollOffset / 2) } ) LazyColumn(state = listState) { // ... } }
Peki bu yöntem neden daha iyi performans gösteriyor? Değiştiriciye sağladığınız lambda bloğu, düzen aşamasında (özellikle düzen aşamasının yerleştirme adımında) çağrılır. Bu nedenle, firstVisibleItemScrollOffset
durumu artık oluşturma sırasında okunmaz. Compose, durumun ne zaman okunduğunu izlediğinden bu değişiklik, firstVisibleItemScrollOffset
'nın value
değiştiğinde Compose'un yalnızca düzen ve çizim aşamalarını yeniden başlatması gerektiği anlamına gelir.
Elbette, durumları kompozisyon aşamasında okumak genellikle kesinlikle gereklidir. Bununla birlikte, durum değişikliklerini filtreleyerek yeniden oluşturma sayısını en aza indirebileceğiniz durumlar vardır. Bu konu hakkında daha fazla bilgi için derivedStateOf
: Bir veya daha fazla durum nesnesini başka bir duruma dönüştürme başlıklı makaleyi inceleyin.
Yeniden oluşturma döngüsü (döngüsel aşama bağımlılığı)
Bu kılavuzda daha önce, Compose'un aşamalarının her zaman aynı sırada çağrıldığı ve aynı çerçevedeyken geriye gitmenin mümkün olmadığı belirtiliyordu. Ancak bu, uygulamaların farklı karelerde kompozisyon döngülerine girmesini engellemez. Aşağıdaki örneğe bakın:
Box { var imageHeightPx by remember { mutableStateOf(0) } Image( painter = painterResource(R.drawable.rectangle), contentDescription = "I'm above the text", modifier = Modifier .fillMaxWidth() .onSizeChanged { size -> // Don't do this imageHeightPx = size.height } ) Text( text = "I'm below the image", modifier = Modifier.padding( top = with(LocalDensity.current) { imageHeightPx.toDp() } ) ) }
Bu örnekte, üstte resim, altta ise metin olacak şekilde dikey bir sütun uygulanıyor. Resmin çözümlenmiş boyutunu almak için Modifier.onSizeChanged()
, metni aşağı kaydırmak için ise Modifier.padding()
kullanılır.
Px
kodundan Dp
koduna yapılan doğal olmayan dönüşüm, kodda bir sorun olduğunu gösterir.
Bu örnekteki sorun, kodun tek bir çerçeve içinde "son" düzene ulaşmamasıdır. Kod, birden fazla karenin gerçekleşmesine bağlıdır. Bu durum, gereksiz iş yapılmasına ve kullanıcı için kullanıcı arayüzünün ekranda atlamasına neden olur.
İlk kare kompozisyonu
İlk karenin oluşturma aşamasında imageHeightPx
başlangıçta 0
olur. Sonuç olarak, kod metne Modifier.padding(top = 0)
ekler.
Sonraki düzen aşamasında onSizeChanged
değiştiricisinin geri çağırma işlevi çağrılır. Bu işlev, imageHeightPx
değerini resmin gerçek yüksekliğiyle günceller. Oluşturur ve sonraki kare için yeniden oluşturma planlar. Ancak mevcut çizim aşamasında, güncellenen imageHeightPx
değeri henüz yansıtılmadığı için metin 0
dolgusuyla oluşturulur.
İkinci çerçeve kompozisyonu
Oluşturma, imageHeightPx
değerindeki değişiklikle tetiklenen ikinci kareyi başlatır. Bu çerçevenin oluşturma aşamasında durum, Box
içerik bloğunda okunur. Metin artık resmin yüksekliğiyle tam olarak eşleşen dolguyla sağlanıyor. Düzen aşamasında imageHeightPx
tekrar ayarlanır ancak değer tutarlı kaldığı için başka yeniden oluşturma planlanmaz.
Bu örnek yapay görünebilir ancak bu genel kalıba dikkat edin:
Modifier.onSizeChanged()
,onGloballyPositioned()
veya başka bir düzen işlemleri- Durumu güncelleme
- Bu durumu bir düzen değiştiriciye (
padding()
,height()
veya benzeri) giriş olarak kullanın. - Tekrar etme ihtimali
Yukarıdaki örnek için düzeltme, uygun düzen öğelerini kullanmaktır. Önceki örnek Column()
ile uygulanabilir ancak özel bir şey gerektiren daha karmaşık bir örneğiniz olabilir. Bu durumda özel bir düzen yazmanız gerekir. Daha fazla bilgi için Özel düzenler rehberine bakın.
Buradaki genel ilke, birbirine göre ölçülüp yerleştirilmesi gereken birden fazla kullanıcı arayüzü öğesi için tek bir doğruluk kaynağına sahip olmaktır. Uygun bir düzen öğesi kullanmak veya özel bir düzen oluşturmak, paylaşılan en küçük üst öğenin, birden fazla öğe arasındaki ilişkiyi koordine edebilecek doğruluk kaynağı olarak hizmet verdiği anlamına gelir. Dinamik bir durumun tanıtılması bu ilkeyi ihlal eder.
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir.
- State ve Jetpack Compose
- Listeler ve ızgaralar
- Jetpack Compose için Kotlin