Diğer çoğu kullanıcı arayüzü araç seti gibi Compose da kareleri birkaç farklı aşama üzerinden oluşturur. Örneğin, Android View sisteminin üç ana aşaması vardır: ölçüm, düzen ve çizim. Compose da buna çok benzer ancak başlangıçta oluşturma adında önemli bir ek aşama vardır.
Compose belgelerinde, oluşturma işlemi Compose'da düşünme ve Durum ve Jetpack Compose bölümlerinde açıklanmaktadır.
Karelerin üç aşaması
Compose'un üç ana aşaması vardır:
- Oluşturma: Hangi kullanıcı arayüzünün gösterileceğini belirler. 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 nerede yer alacağı. 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 tüm alt öğeleri 2D koordinatlarda ölçer ve yerleştirir.
- Çizim: Nasıl oluşturulur? Kullanıcı arayüzü öğeleri, genellikle cihaz ekranı olan bir tuvalde çizilir.
Bu aşamaların sırası genellikle aynıdır. Bu sayede veriler, bir kare oluşturmak için oluşturmadan düzene ve çizime doğru tek yönde akar (tek yönlü veri akışı olarak da bilinir). BoxWithConstraints, LazyColumn ve LazyRow, alt öğelerinin oluşturulması üst öğenin düzen aşamasına bağlı olduğu için dikkate değer istisnalardır.
Kavramsal olarak, bu aşamaların her biri her kare için gerçekleşir. Ancak performansı optimize etmek için Compose, tüm bu aşamalarda aynı girişlerden aynı sonuçları hesaplayacak çalışmaları tekrarlamaktan kaçınır. Compose, önceki bir sonucu yeniden kullanabiliyorsa composable işlev çalıştırmayı atlar. Compose kullanıcı arayüzü, gerekmedikçe tüm ağacı yeniden düzenlemez veya yeniden çizmez. Oluşturma, kullanıcı arayüzünü güncellemek için gereken minimum miktarda işlem yapar. Compose, farklı aşamalardaki durum okumalarını izlediği için 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ı bir şekilde açıklanmaktadır.
Beste
Oluşturma aşamasında, Compose ç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ı, sonraki aşamalar için gereken tüm bilgileri içeren düzen düğümlerinden oluşur. Bu durum aşağıdaki videoda gösterilmektedir:
Şekil 2. Oluşturma aşamasında oluşturulan ve kullanıcı arayüzünüzü temsil eden ağaç.
Kodun 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 composable işlev, kullanıcı arayüzü ağacındaki tek bir düzen düğümüyle eşlenir. Daha karmaşık örneklerde, composable'lar mantık ve kontrol akışı içerebilir ve farklı durumlar verildiğinde farklı bir ağaç oluşturabilir.
Düzen
Düzen aşamasında Compose, giriş olarak oluşturma aşamasında üretilen kullanıcı arayüzü ağacını kullanır. Düzen düğümleri koleksiyonu, her düğümün 2D uzaydaki boyutuna 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 bir 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 taranır:
- Alt öğeleri ölçme: Bir düğüm, varsa alt öğelerini ölçer.
- Kendi boyutuna karar verme: Bu ölçümlere göre düğüm, kendi boyutuna karar verir.
- Alt öğeleri 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ış width ve height
- Çizilmesi gereken bir 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 öğeleri olanImageveColumnöğelerini ölçer.Imageölçülür. Alt öğesi olmadığı için kendi boyutuna karar verir ve boyutuRowöğesine bildirir.- Ardından
Columnölçülür. Önce kendi alt öğelerini (ikiTextoluşturulabilir öğe) ölçer. - İlk
Textölçülür. Alt öğesi olmadığı için kendi boyutuna karar verir 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 alt öğe ölçümlerini kullanır. Alt öğelerin maksimum genişliğini ve alt öğelerin yüksekliklerinin toplamını kullanır.Column, alt öğelerini kendisine göre yerleştirerek dikey olarak alt alta koyar.Row, kendi boyutuna karar vermek için alt öğe ölçümlerini kullanır. Alt öğelerin maksimum 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ğine dikkat edin. Compose çalışma zamanı, tüm düğümleri ölçmek ve yerleştirmek için kullanıcı arayüzü ağacından yalnızca bir kez geçiş yapılmasını gerektirir. Bu da performansı artırır. Ağaçtaki düğüm sayısı arttığında ağaçta gezinmek için harcanan süre doğrusal olarak artar. Buna karşılık, her düğüm birden çok kez ziyaret edilirse geçiş süresi katlanarak artar.
Çizim
Çizim aşamasında ağaç, yukarıdan aşağıya doğru tekrar taranır ve her düğüm sırayla ekranda çizilir.
Şekil 5. Çizim aşamasında pikseller ekrana çizilir.
Önceki örnekte ağaç içeriği şu şekilde çizilir:
Row, arka plan rengi gibi sahip olabileceği tüm içerikleri çizer.Imagekendi kendini çiziyor.Columnkendi kendini çiziyor.- İlk ve ikinci
Textsırayla çizilir.
Şekil 6. Kullanıcı arayüzü ağacı ve çizilmiş temsili.
Durum okumaları
Daha önce listelenen aşamalardan birinde bir snapshot state öğesinin value değerini okuduğunuzda Compose, value değerini okurken ne yaptığını otomatik olarak takip eder. Bu izleme, durumun value değeri 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 yöntemden biriyle bu duruma erişirsiniz: value özelliğine doğrudan erişerek veya Kotlin özellik temsilcisi kullanarak. Birleştirilebilir öğelerde durum bölümünden bu konu hakkında daha fazla bilgi edinebilirsiniz. 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 alıcı ve ayarlayıcı işlevleri, yalnızca özelliğe 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, yeniden başlatma kapsamı olarak adlandırılır. Compose, farklı aşamalarda durum value değişikliklerini ve yeniden başlatma kapsamlarını takip eder.
Aşamalı durum okumaları
Daha önce de belirtildiği gibi, Compose'da üç ana aşama vardır ve Compose, her birinde hangi durumun okunduğunu takip eder. Bu sayede Compose, yalnızca kullanıcı arayüzünüzün etkilenen her bir öğesi için işlem yapması gereken belirli aşamaları bilgilendirebilir.
Aşağıdaki bölümlerde her aşama ve bir durum değeri okunduğunda ne olduğu açıklanmaktadır.
1. Aşama: Oluşturma
@Composable işlevi veya lambda bloğu içindeki durum okumaları, oluşturmayı ve potansiyel olarak sonraki aşamaları etkiler. Durumun value değeri değiştiğinde yeniden oluşturucu, bu 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 bir kısmını veya tamamını atlamaya karar verebileceğini unutmayın. Daha fazla bilgi için Girişler değişmediyse atlama bölümüne bakın.
Oluşturma işleminin sonucuna bağlı olarak Compose kullanıcı arayüzü, 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şim. Ölçüm adımı, Layout composable'ına iletilen ölçüm lambda'sı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 okumaları, düzeni ve potansiyel olarak çizim aşamasını etkiler. Durumun value değeri 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. Yaygın örnekler arasında Canvas(), Modifier.drawBehind ve Modifier.drawWithContent yer alır. Durumun value değeri değiştiğinde Compose kullanıcı arayüzü 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
Oluşturma, yerelleştirilmiş durum okuma izleme işlemi gerçekleştirdiğinden her durumu uygun bir aşamada okuyarak gerçekleştirilen iş miktarını en aza indirebilirsiniz.
Aşağıdaki örneği inceleyelim. Bu örnekte, son düzen konumunu dengelemek için offset değiştiricisini kullanan bir Image() vardır. Bu, kullanıcı sayfayı kaydırdığında paralaks efekti oluşturur.
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ışır ancak performansın düşmesine neden olur. Yazıldığı şekliyle kod, firstVisibleItemScrollOffset eyaletinin value değerini okur ve Modifier.offset(offset: Dp) işlevine iletir. Kullanıcı sayfayı kaydırdıkça firstVisibleItemScrollOffset öğesinin value değeri değişir. Öğrendiğiniz gibi, Compose, okuma kodunu yeniden başlatabilmek (yeniden çağırabilmek) için tüm durum okumalarını izler. Bu örnekte okuma kodu, Box içeriğidir.
Bu, oluşturma aşamasında bir durumu okumaya örnektir. Bu durum her zaman kötü bir şey değildir. Hatta yeniden oluşturmanın temelini oluşturur ve veri değişikliklerinin yeni kullanıcı arayüzü oluşturmasına olanak tanır.
Önemli nokta: Bu örnek, her kaydırma etkinliğinde tüm composable içeriğin yeniden değerlendirilmesine, ölçülmesine, düzenlenmesine ve son olarak çizilmesine neden olduğu için ideal değildir. Gösterilen içerik değişmese de her kaydırma işleminde oluşturma aşamasını tetiklersiniz. Yalnızca konumu değişir. Durum okuma işlemini yalnızca düzen aşamasını yeniden tetikleyecek şekilde optimize edebilirsiniz.
Lambda ile dengeleme
Ofset değiştiricinin başka bir sürümü daha vardır:
Modifier.offset(offset: Density.() -> IntOffset).
Bu sürüm, lambda bloğu tarafından döndürülen sonuç ofsetinin bir lambda parametresi alı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 performanslı? 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, firstVisibleItemScrollOffset durumunun artık oluşturma sırasında okunmadığı anlamına gelir. Oluşturma, durumun ne zaman okunduğunu takip eder.
Bu değişiklik, firstVisibleItemScrollOffset öğesinin value değeri değişirse
Oluşturma'nın yalnızca düzen ve çizim aşamalarını yeniden başlatması gerektiği anlamına gelir.
Elbette, oluşturma aşamasında durumları okumak genellikle kesinlikle gereklidir. Buna rağmen, 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, Oluşturma aşamalarının her zaman aynı sırayla çağrıldığı ve aynı karede geriye gitmenin bir yolu olmadığı belirtilmişti. Ancak bu, uygulamaların farklı karelerde oluşturma döngülerine girmesini engellemez. Aşağıdaki örneğe bakın:
Box { var imageHeightPx by remember { mutableIntStateOf(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 ve altında metin bulunan dikey bir sütun uygulanmaktadır. Resmin çözümlenen boyutunu almak için Modifier.onSizeChanged(), metni aşağı kaydırmak için ise Modifier.padding() kullanır.
Px değerinden Dp değerine yapılan doğal olmayan dönüşüm, kodda bir sorun olduğunu gösterir.
Bu örnekteki sorun, kodun tek bir karede "nihai" düzene ulaşmamasıdır. Kod, birden fazla kareye dayanır. Bu da gereksiz işler yapar ve kullanıcı arayüzünün ekranda atlamasına neden olur.
İlk kare oluşturma
İlk karenin oluşturma aşamasında imageHeightPx başlangıçta 0 olur. Bu nedenle kod, metni Modifier.padding(top = 0) ile sağlar.
Sonraki düzen aşaması, onSizeChanged değiştiricisinin geri çağırma işlevini çağırır.
Bu işlev, imageHeightPx değerini resmin gerçek yüksekliğine günceller. Compose, bir sonraki kare için yeniden oluşturma işlemi planlar. Ancak geçerli çizim aşamasında, güncellenen imageHeightPx değeri henüz yansıtılmadığı için metin 0 dolgusuyla oluşturulur.
İkinci kare kompozisyonu
Compose, imageHeightPx değerindeki değişiklikle tetiklenen ikinci kareyi başlatır. Bu karenin 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ır. Düzen aşamasında imageHeightPx tekrar ayarlanır ancak değer tutarlı kaldığı için başka bir yeniden oluşturma işlemi planlanmaz.
Bu örnek yapay görünebilir ancak şu genel kalıba dikkat edin:
Modifier.onSizeChanged(),onGloballyPositioned()veya başka bir düzen işlemi- Bir durumu güncelleme
- Bu durumu bir düzen değiştiriciye (
padding(),height()veya benzeri) giriş olarak kullanın. - Tekrarlanabilir
Önceki örnekteki sorunu düzeltmek için uygun düzen temel öğelerini kullanmak gerekir. Önceki örnek Column() ile uygulanabilir ancak özel bir düzen yazmayı gerektiren daha karmaşık bir örneğiniz olabilir. Daha fazla bilgi için Özel düzenler kılavuzuna bakın.
Buradaki genel ilke, birbirine göre ölçülmesi ve yerleştirilmesi gereken birden fazla kullanıcı arayüzü öğesi için tek doğruluk kaynağına sahip olmaktır. Doğru bir düzen temel öğesi kullanmak veya özel bir düzen oluşturmak, minimum paylaşılan üst öğenin birden fazla öğe arasındaki ilişkiyi koordine edebilen doğruluk kaynağı olarak hizmet ettiği anlamına gelir. Dinamik bir durumun kullanılması bu ilkeyi ihlal eder.
Yeniden oluşturma döngüleri ve aşamalar arasında duruma yazmaktan nasıl kaçınacağınız hakkında daha fazla bilgi için Oluşturma'da geriye doğru yazma başlıklı makaleyi inceleyin.
Sizin için önerilenler
- Not: JavaScript devre dışı bırakıldığında bağlantı metni gösterilir.
- State ve Jetpack Compose
- Listeler ve ızgaralar
- Jetpack Compose için Kotlin