onLayout(), üst konteynerin koordinat düzleminde alt View'ların konumlarını ve boyutlarını belirleyen ViewGroup sınıfının bir metodudur. Android sistemi, ölçüm aşamasından (onMeasure) sonra, her alt View için ölçülen genişlik ve yükseklik zaten bilindiğinde onLayout'u çağırır. Android Developers Documentation (2026)'ya göre, standart ViewGroup uygulaması otomatik alt eleman konumlandırması yapmadığından, onLayout herhangi bir özel ViewGroup'da geçersiz kılınması zorunlu bir metottur.
Önemli Noktalar
onLayout(boolean changed, int l, int t, int r, int b), sistem tarafından üst konteyner içindeki alt View'ları konumlandırmak için çağrılan ViewGroup sınıfının protected bir metodudur. Geliştirici, standart olmayan eleman düzenine (kaskad, ızgara, dama deseni veya rastgele koordinatlar) sahip özel bir ViewGroup oluşturduğunda bu metodu geçersiz kılar. Her alt View, child.layout() çağrısı aracılığıyla nihai sınırlarını alır.
changed parametresi, son düzenden bu yana ViewGroup'un konumunun veya boyutunun değişip değişmediğini gösterir. changed true ise, tüm alt elemanların muhtemelen yeniden konumlandırılması gerekir. l, t, r, b parametreleri, ViewGroup'un üstünün koordinat sisteminde sol üst ve sağ alt köşelerinin koordinatlarıdır. onLayout içinde geliştirici, bu değerleri alt elemanların düzenlenmesi için başlangıç koordinatları olarak kullanır.
ViewGroup, onLayout'u geçersiz kılan tek sınıftır. Normal bir View (ViewGroup olmayan) alt elemanlara sahip değildir ve onLayout'a ihtiyaç duymaz — konumlandırması üst konteyner tarafından yönetilir. Normal bir View onLayout'u geçersiz kılsa bile, sistem onu çağırmaz. Bu, herhangi bir View için çağrılan onMeasure'den temel bir farktır.
Düzen aşaması, kök View üzerinde public metot layout(int l, int t, int r, int b) çağrısıyla başlar. Bu metot, View'in kendi nihai koordinatlarını belirler ve View bir ViewGroup ise onLayout'u çağırır. Ardından onLayout, her alt eleman için yinelemeli olarak child.layout() çağırır ve süreç hiyerarşide aşağı doğru tekrarlanır. Böylece, düzen kökten yapraklara doğru yayılır.
onLayout çağrılmadan önce sistem, View'in boyutlarının önceki döngüye göre değişip değişmediğini kontrol eder. Boyutlar değişmemişse ve requestLayout çağrılmamışsa, onLayout çağrılmayabilir — sistem önceki düzenin sonuçlarını kullanır. Bu, yalnızca içeriğin değiştiği ancak boyutların değişmediği animasyonlar veya kaydırma sırasında gereksiz konum yeniden hesaplamalarını önleyen bir optimizasyondur.
requestLayout(), View'in düzeninin güncelliğini yitirdiğini ve yeniden hesaplanması gerektiğini sisteme bildiren bir View metodudur. requestLayout çağırmak tam bir döngüyü tetikler: önce onMeasure çağrılır, sonra onLayout, sonra onDraw. Yalnızca yeniden çizmeyi tetikleyen invalidate'in aksine, requestLayout boyutların ve konumların tamamen yeniden hesaplanmasını tetikler. requestLayout'a aşırı çağrılar performans sorunlarının yaygın bir nedenidir.
l (left) — ViewGroup'un üstünün koordinat sisteminde sol kenarının X koordinatı. t (top) — üst kenarın Y koordinatı. r (right) — sağ kenarın X koordinatı. b (bottom) — alt kenarın Y koordinatı. ViewGroup'un genişliği r - l, yüksekliği b - t olarak hesaplanır. Bu koordinatlar zaten ViewGroup'un tüm padding'ini içerir.
onLayout içinde geliştirici, her alt View için child.layout(int childLeft, int childTop, int childRight, int childBottom) çağırır. child.layout'e iletilen koordinatlar, üst ViewGroup'un koordinat sisteminde olmalıdır. Tipik olarak childLeft ve childTop, üstün padding'i dikkate alınarak hesaplanır: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.
| Parametre | Açıklama | Tipik Kullanım |
|---|---|---|
| l (left) | Üst öğede ViewGroup'un sol kenar koordinatı | Alt elemanlar için X ekseninde başlangıç noktası |
| t (top) | Üst öğede ViewGroup'un üst kenar koordinatı | Alt elemanlar için Y ekseninde başlangıç noktası |
| r (right) | Üst öğede ViewGroup'un sağ kenar koordinatı | Genişlik üst sınırı, r - l = getWidth() |
| b (bottom) | Üst öğede ViewGroup'un alt kenar koordinatı | Yükseklik üst sınırı, b - t = getHeight() |
Alt koordinatlar şu formülle hesaplanır: childLeft = l + paddingLeft + (marginLeft varsa), childRight = childLeft + child.getMeasuredWidth(). Dikey eksen için benzer şekilde: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). Bu dört değer hesaplandıktan sonra child.layout(childLeft, childTop, childRight, childBottom) çağrılır.
Bir FlowLayout oluşturalım — alt View'ları satırlar halinde düzenleyen ve mevcut satır dolduğunda elemanları yeni bir satıra kaydıran özel bir ViewGroup. Bu, tek bir düzlemde wrap ile Flexbox'ın karşılığıdır. onLayout tüm alt View'ları yineler, her biri için konumu hesaplar ve doğru sınırlarla child.layout() çağırır.
class FlowLayout(context: Context)
: ViewGroup(context) {
private val horizontalSpacing = 12
private val verticalSpacing = 8
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
val parentWidth =
MeasureSpec.getSize(widthMeasureSpec)
var rowX = paddingLeft
var rowY = paddingTop
var maxRowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChildWithMargins(child,
widthMeasureSpec, 0,
heightMeasureSpec, 0)
if (rowX + child.measuredWidth >
parentWidth - paddingRight) {
rowX = paddingLeft
rowY += maxRowHeight + verticalSpacing
maxRowHeight = 0
}
rowX += child.measuredWidth +
horizontalSpacing
maxRowHeight = maxOf(maxRowHeight,
child.measuredHeight)
}
val totalHeight = rowY + maxRowHeight +
paddingBottom
setMeasuredDimension(
resolveSize(parentWidth, widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec))
}
override fun onLayout(changed: Boolean,
l: Int, t: Int,
r: Int, b: Int) {
val parentWidth = r - l
var rowX = paddingLeft
var rowY = paddingTop
var maxRowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
val cw = child.measuredWidth
val ch = child.measuredHeight
if (rowX + cw >
parentWidth - paddingRight) {
rowX = paddingLeft
rowY += maxRowHeight + verticalSpacing
maxRowHeight = 0
}
child.layout(rowX, rowY,
rowX + cw, rowY + ch)
rowX += cw + horizontalSpacing
maxRowHeight =
maxOf(maxRowHeight, ch)
}
}
override fun generateLayoutParams(attrs: AttributeSet?)
: LayoutParams =
MarginLayoutParams(context, attrs)
}
onMeasure ve onLayout, View yaşam döngüsünün temelde farklı görevler gerçekleştiren iki ardışık aşamasıdır. onMeasure bir View'in istenen (ölçülen) boyutlarını belirlerken, onLayout gerçek (nihai) koordinatları ve boyutları belirler. Temel fark: onMeasure'de boyutlar geçici olabilir ve daha sonra üst tarafından ayarlanabilirken, onLayout'da her alt View'in nihai konumu sabitlenir.
onMeasure, yaprak View'lar (TextView, ImageView, Button) dahil her View için çağrılır. onLayout yalnızca ViewGroup için çağrılır. Bunun nedeni, konumlandırmanın View'in kendisinin değil, üst konteynerin sorumluluğu olmasıdır. Bir yaprak View, üstün onLayout'undan çağrılan layout() aracılığıyla konumunu alır.
getMeasuredWidth() ve getMeasuredHeight() onMeasure'den sonra kullanılabilirken, getWidth() ve getHeight() yalnızca onLayout'dan sonra kullanılabilir. onMeasure içinde getWidth()'e erişirseniz, önceki döngünün değerini veya sıfırı döndürür. Bu nedenle, onMeasure'de boyutları hesaplamak için MeasureSpec ve alt öğeleri sırayla kullanmalısınız.
Padding'i hesaba katmadan konumlandırma — onLayout'u uygularken ilk hata. Geliştirici genellikle alt View'ların başlangıç koordinatlarına üstün paddingLeft ve paddingTop değerlerini eklemeyi unutur. Sonuç olarak, alt öğeler setPadding() veya XML işaretlemesiyle ayarlanan padding'i yok sayarak ViewGroup'un kenarında görüntülenir. Doğru hesaplama: childLeft = paddingLeft + offsetX.
Görünmez alt öğeler için layout çağırma — ikinci yaygın sorun. Bir ViewGroup, görünürlüğü GONE olan alt View'lar içeriyorsa, bunların konumlandırılması gerekmez — yer kaplamazlar. Ancak onLayout, GONE alt öğelerini atlayarak bu durumu doğru şekilde ele almalıdır. INVISIBLE alt öğeler için layout yine de çağrılmalıdır — görüntülenmeseler bile yerlerini korurlar.
changed parametresini yok sayma — üçüncü hata. changed parametresi, ViewGroup'un boyutlarının veya konumunun değişip değişmediğini gösterir. changed == false ise, tüm alt elemanların düzeni yeniden hesaplanmadan önbelleğe alınmış koordinatlar kullanılabilir. Ancak, tam düzen önbelleğe alma karmaşık bir iştir ve çoğu uygulamada onLayout her seferinde basitçe tüm öğeleri yeniden hesaplar. Bu, az sayıda alt öğe ile kabul edilebilir.
Sıkça Sorulan Sorular
Evet, mümkün ViewGroup standart LayoutParams kullanıyorsa ve özel konumlandırma mantığı eklemiyorsa. Ancak, ViewGroup'daki standart onLayout uygulaması hiçbir işlem yapmaz — alt elemanlar konumlandırılmaz. Pratikte, tüm ViewGroup'lar (LinearLayout, RelativeLayout, FrameLayout) onLayout'u geçersiz kılar.
layout(), sistem veya üst ViewGroup tarafından çağrılan View'in public final bir metodudur. View'in kendi koordinatlarını belirler ve View bir ViewGroup ise onLayout'u çağırır. onLayout(), geliştiricinin alt elemanların özel düzenlenmesi için geçersiz kıldığı protected bir metottur.
Teknik olarak — evet, çağırabilir. Ancak bu kesinlikle önerilmez, çünkü sonsuz özyinelemeye yol açar: requestLayout → onMeasure → onLayout → requestLayout. onLayout içinde requestLayout çağrılırsa, sistem StackOverflowError istisnası fırlatır. Tüm boyut değişiklikleri onLayout'dan önce yapılmalıdır.
Düzen animasyonları (LayoutTransition), alt View konumlarındaki değişiklikleri yakalar ve geçiş animasyonu uygular. LayoutTransition etkinleştirildiğinde, onLayout önce nihai konumları belirler, ardından LayoutTransition eski konumdan yeni konuma hareketi canlandırır. Bu, uygun nihai koordinatlarla doğru bir onLayout uygulaması gerektirir.
invalidate() yalnızca draw aşamasını (yeniden çizme) tetikler, measure ve layout'u etkilemez. onLayout'u tetiklemek için, tam döngüyü başlatan requestLayout() çağrılmalıdır: measure → layout → draw. invalidate, boyutlar ve konumlar değişmediğinde görünümü güncellemek için daha verimlidir.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun