Android'de Görünüm'ün çalışma prensibinin derinlemesine analizi (ortada)

Bir önceki makaleye devam ediyoruz, bu makale Görünümün çizim sürecini açıklıyor:

Çizim sürecini görüntüle

Ölçme süreci

Eğer sadece bir View ise, o zaman ölçüm süreci ölçüm metodu ile tamamlanır.Eğer bu bir ViewGroup ise, kendini ölçmenin yanı sıra, çocuğun ölçüm metodu da çağrılacaktır.

1. Görünümün ölçüm süreci

View'un ölçüm süreci, aşağıdaki paragrafı içeren ölçüm yöntemiyle tamamlanır:

......... int cacheIndex = (mPrivateFlags ve PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT? -1: mMeasureCache.indexOfKey (anahtar); eğer (cacheIndex < 0 || sIgnoreMeasureCache) { // kendimizi ölçün, bu ölçülen boyut bayrağını geri ayarlamalı onMeasure (widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 = ~ PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } Başka { uzun değer = mMeasureCache.valueAt (cacheIndex); // long'dan int'e çevirmek yüksek 32 biti düşürür, maskeye gerek yoktur setMeasuredDimensionRaw ((int) (değer > > 32), (int) değeri); mPrivateFlags3 | = PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } .........

View'un ölçü yönteminde, aslında kendi onMeasure yöntemini çağırdığını bilirsiniz.

korumalı void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension (getDefaultSize (getSuggestedMinimumWidth (), widthMeasureSpec), getDefaultSize (getSuggestedMinimumHeight (), heightMeasureSpec)); } // içinde getDefaultSize yöntemi var public static int getDefaultSize (int size, int measureSpec) { int sonuç = boyut; int specMode = MeasureSpec.getMode (measureSpec); int specSize = MeasureSpec.getSize (measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: sonuç = boyut; kırmak; case MeasureSpec.AT_MOST: vaka Ölçü Spesifikasyonu TAMAMEN: sonuç = specSize; kırmak; } dönüş sonucu; }

Genel olarak, yalnızca MeasureSpec.AT_MOST ve MeasureSpec.EXACTLY'nin iki durumuna bakmamız gerekir. Bu iki durumda döndürülen sonuç, aslında measureSpec'de elde edilen specSize'dir. Bu specSize, ölçülen Görünümün boyutudur ve buradaki neden, ölçülen Görünüm'ün boyutudur. , Görünümün nihai boyutu düzen aşamasında belirlendiğinden, ayrımın eklenmesi gerekir.Genel olarak, Görünümün ölçülen boyutu son boyutla aynıdır.

UNSPECIFIED durumunda, sonucun değeri getSuggestedMinimumWidth () yöntemi ve getSuggestedMinimumHeight () tarafından döndürülen değerdir. Şu iki yöntemi kontrol edin:

korumalı int getSuggestedMinimumWidth () { dönüş (mBackground == null)? mMinWidth: max (mMinWidth, mBackground.getMinimumWidth ()); } korumalı int getSuggestedMinimumHeight () { dönüş (mBackground == null)? mMinHeight: max (mMinHeight, mBackground.getMinimumHeight ()); }

GetSuggestedMinimumWidth kodundan, Görünüm'ün bir arka plan ayarı yoksa, genişliğin android: minWidth özelliği tarafından ayarlanan değere karşılık gelen mminWidth olduğu görülebilir.Görünümün bir arka planı varsa, maksimumdur (mMinWidth, mBackground.getMinimumWidth () ):

public int getMinimumWidth () { final int intrinsicWidth = getIntrinsicWidth (); dönüş intrinsicWidth > 0? İç Genişlik: 0; }

MBackground.getMinimumWidth () yöntemine bakın.Aslında bir Drawable yöntemdir. İntrinsicHeight, yani orijinal genişlik 0 değilse, 0 ise geri döndürün.

Viewun getDefaultSize yönteminden, Viewun genişliğinin ve yüksekliğinin specSize tarafından belirlendiği sonucuna varılabilir. Kontrolü View miras alarak özelleştirirsek, onMeasure yöntemini geçersiz kılmamız ve WRAP_CONTENT boyutunu ayarlamamız gerekir. Aksi takdirde, mizanpajda WRAP_CONTENT kullanmak MATCH_PARENT kullanmaya eşdeğerdir.

Sebep: View, mizanpajda specMode'a eşdeğer olması için WRAP_CONTENT kullandığından AT_MOST ve bu durumda, sonuç = specSize, bu specSize'nin boyutu parentSize, parentSize, üst kapsayıcının mevcut kalan alanı olan üst kapsayıcının şu anda kullanılabilen boyutudur Boyut, ardından efekt, düzende MATCH_PARENT kullanmakla aynıdır.

Bu nedenle, AT_MOST modunda, genellikle View için varsayılan dahili genişlik ve yüksekliği ayarlarız ve bu genişlik ve yüksekliği WRAP_CONTENT olarak ayarlarız. TextView ve ImageView kaynak kodunu kontrol ederek WRAP_CONTENT altında onMeasure yönteminin tamamlandığını öğrenebilirsiniz. Özel işleme için, aşağıda TextView onMeasure öğesinin bir bölümü yer almaktadır:

if (widthMode == MeasureSpec.AT_MOST) { genişlik = Matematik.min (genişlikBoyut, genişlik); }

2. ViewGroup ölçüm süreci

ViewGroup soyut bir sınıftır, View öğesinin onMeasure yöntemini geçersiz kılmaz, ancak bir measureChildren yöntemi sağlar:

korumalı void measureChildren (int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final Görünüm çocukları = mChildren; for (int i = 0; i < boyut; ++ i) { son Görünüm çocuk = çocuklar ; eğer ((child.mViewFlags ve VISIBILITY_MASK)! = GİTTİ) { measureChild (alt, widthMeasureSpec, heightMeasureSpec); } } }

Alt öğeleri çaprazlar ve ardından her bir alt öğenin genişliğini ve yüksekliğini ölçmek için measureChild yöntemini çağırır:

korumalı void measureChild (View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams (); final int childWidthMeasureSpec = getChildMeasureSpec (parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec (parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure (childWidthMeasureSpec, childHeightMeasureSpec); }

Alt görünümü ölçmeden önce, çocuğun MeasureSpec'ini elde etmek için getChildMeasureSpec yöntemi çağrılacaktır.Bu koddan, alt görünümün MeasureSpec'inin belirlenmesinin, üst kapsayıcının MeasureSpec (parentWidthMeasureSpec ve parentHeightMeasureSpec) ve kendi LayoutParams (lp) ile ilişkili olduğu görülebilir. .height ve lp.width) ve Viewun kendi Margin and Paddingi ve son olarak alt görünümün ölçüm yöntemi olarak adlandırılır

ViewGroup, belirli ölçüm sürecini tanımlamaz. Bunun nedeni, ViewGroup'un soyut bir sınıf olması ve onMeasure yönteminin çeşitli alt sınıflar tarafından uygulanması gerektiğidir, çünkü LinearLayout, RelativeLayout, vb. Gibi her ViewGroup uygulama sınıfının düzen yöntemleri farklıdır. Dolayısıyla genel olarak onMeasure yöntemini yazmak imkansızdır.

Ardından, LinearLayout'un onMeasure yöntemini analiz edin:

korumalı void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == DİKEY) { measureVertical (widthMeasureSpec, heightMeasureSpec); } Başka { measureHorizontal (widthMeasureSpec, heightMeasureSpec); } }

Ölçümü görüntüle Dikey yöntem

// Herkesin ne kadar uzun olduğunu görün. Maksimum genişliği de unutmayın. for (int i = 0; i < say; ++ i) { final Görünümü child = getVirtualChildAt (i); if (child == null) { mTotalLength + = measureNullChild (i); devam et; } if (child.getVisibility () == View.GONE) { i + = getChildrenSkipCount (çocuk, i); devam et; } if (hasDividerBeforeChildAt (i)) { mTotalLength + = mDividerHeight; } LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams (); totalWeight + = lp.weight; if (heightMode == MeasureSpec.EXACTLY lp.height == 0 lp.weight > 0) { // Optimizasyon: kullanacak çocukları ölçmeye zahmet etmeyin // kalan alan. Bu görünümler aşağıda yeniden ölçülür, eğer // kalan boşluk var. final int totalLength = mTotalLength; mTotalLength = Math.max (totalLength, totalLength + lp.topMargin + lp.bottomMargin); skippedMeasure = true; } Başka { int oldHeight = Tamsayı.MIN_VALUE; eğer (lp.height == 0 lp.weight > 0) { // heightMode ya UNSPECIFIED ya da AT_MOST'tur ve bu // çocuk mevcut alanı doldurmak için genişletmek istedi. // Bunu WRAP_CONTENT diline çevir ki bitmesin // yüksekliği 0 olan oldHeight = 0; lp.height = LayoutParams.WRAP_CONTENT; } // Bu çocuğun ne kadar büyük olmak istediğini belirleyin. Bu veya // önceki çocuklar kilo verdiler, sonra buna izin verdik // mevcut tüm alanı kullan (ve işleri daha sonra küçülteceğiz // gerekirse). measureChildBeforeLayout ( child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0? mTotalLength: 0); if (oldHeight! = Integer.MIN_VALUE) { lp.height = oldHeight; } final int childHeight = child.getMeasuredHeight (); final int totalLength = mTotalLength; mTotalLength = Math.max (totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset (çocuk)); if (useLargestChild) { mostChildHeight = Math.max (childHeight, en büyükChildHeight); } } / ** * Mümkünse, ek ofseti çocuğun taban çizgisine göre hesaplayın * Daha sonra {@link #getBaseline} sorulduğunda ihtiyacımız olacak. * / eğer ((baselineChildIndex > = 0) (baselineChildIndex == i + 1)) { mBaselineChildTop = mTotalLength; } // taban çizgimiz için bir alt dizin kullanmaya çalışıyorsak, yukarıdaki // defter tutma yalnızca üzerinde çocuk yoksa çalışır // ağırlık geliştiriciye yardım etmekte hızlı başarısız olur. Eğer ben < baselineChildIndex lp.weight > 0) { yeni RuntimeException ("Dizine sahip LinearLayout alt öğesi" + "mBaselineAlignedChildIndex değerinden daha az ağırlığa sahiptir > 0, hangi " + "çalışmaz. Ağırlığı kaldırın ya da ayarlamayın" + "mBaselineAlignedChildIndex."); } boolean matchWidthLocally = false; if (widthMode! = MeasureSpec.EXACTLY lp.width == LayoutParams.MATCH_PARENT) { // Doğrusal düzenin genişliği ölçeklenir ve en az bir // çocuk genişliğimize uymak istediğini söyledi. Bir bayrak ayarlayın // en azından bu görünümü yeniden ölçmemiz gerektiğini belirten // genişliğimizi biliyoruz. matchWidth = true; matchWidthLocally = true; } final int margin = lp.leftMargin + lp.rightMargin; final int measureWidth = child.getMeasuredWidth () + margin; maxWidth = Math.max (maxWidth, measureWidth); childState = combMeasuredStates (childState, child.getMeasuredState ()); allFillParent = allFillParent lp.width == LayoutParams.MATCH_PARENT; eğer (lp.weight > 0) { / * * Ağırlıklı Görünümlerin genişlikleri sahtedir * yeniden ölçüm, bu yüzden onları ayrı tutun. * / weightedMaxWidth = Math.max (weightedMaxWidth, matchWidthLocally? margin: measureWidth); } Başka { AlternativeMaxWidth = Math.max (alternativeMaxWidth, matchWidthLocally? margin: measureWidth); } i + = getChildrenSkipCount (çocuk, i); }

Alt öğeleri çaprazlayın ve measureChildBeforeLayout yöntemini çağırın.Bu yöntemde, alt öğenin genişliği ve yüksekliği ölçülür ve LinearLayout'un ilk yüksekliğini dikey yönde kaydetmek için bir mTotalLength vardır.Bir alt öğe her ölçüldüğünde, mTotalLength artar ve eklenen parça çocuğu içerir Öğenin yüksekliği ve alt öğenin dikey kenar boşluğu

void measureChildBeforeLayout (View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight) { measureChildWithMargins (child, widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight); } Çocuğun ölçü yöntemi olan child.measure yöntemini çağırır. korumalı void measureChildWithMargins (Alt öğe, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams (); final int childWidthMeasureSpec = getChildMeasureSpec (parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec (parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child.measure (childWidthMeasureSpec, childHeightMeasureSpec); }

Alt elemanlar ölçüldüğünde, LinearLayout kendi boyutunu ölçecektir.Dikey LinearLayout için, yatay yöndeki ölçüm süreci View'un ölçüm sürecini takip eder.Dikey yönde, match_parent veya belirli değerler kullanılıyorsa , Daha sonra ölçüm süreci View ile tutarlıdır, yani yükseklik specSize; düzenindeki yükseklik wrap_content kullanıyorsa, bu durumda yükseklik, alt öğeler tarafından işgal edilen toplam yüksekliktir, ancak bu toplam, elbette üst konteynerin kalan alanını aşamaz. Dolgu düşünüldüğünde, dikey sonuç aşağıdaki koddan öğrenilebilir:

public static int resolverSizeAndState (int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode (measureSpec); final int specSize = MeasureSpec.getSize (measureSpec); nihai int sonucu; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < boyut) { sonuç = specSize | MEASURED_STATE_TOO_SMALL; } Başka { sonuç = boyut; } kırmak; vaka Ölçü Spesifikasyonu TAMAMEN: sonuç = specSize; kırmak; case MeasureSpec.UNSPECIFIED: varsayılan: sonuç = boyut; } dönüş sonucu | (childMeasuredState ve MEASURED_STATE_MASK); }

Bazen onMeasure'da elde edilen ölçülen genişlik ve yükseklik yanlış olabilir. Daha iyi bir alışkanlık, View'in ölçülen genişlik ve yüksekliğini ve onLayout'ta nihai genişliği ve yüksekliği elde etmektir.

Activity'de, View'un genişlik ve yükseklik bilgileri onCreate, onStart ve onResume'da doğru bir şekilde elde edilemez. Bunun nedeni, önlemin yaşam döngüsünün ve Activity'in senkronize edilmemiş olmasıdır, bu nedenle View'in ölçülmemiş olması ve elde edilen genişlik ve yüksekliğin 0 olması çok olasıdır.

ölçü özeti

1. Ölçüm süreci, esas olarak, view.measure yöntemini üst düzey üst düzey View'dan child View'a yinelemeli olarak çağırma sürecidir (onMeasure yöntemi, ölçümde geri çağrılır). Spesifik önlemin özünde esas olarak aşağıdaki noktalar bulunur:

2. MeasureSpec (dahili View of View) ölçüm spesifikasyonu int tipidir ve değer, yüksek 2-bit özellik modu specMode ve düşük 30-bitlik spesifik boyut specSize'den oluşur. SpecMode için yalnızca üç değer vardır:

MeasureSpec.EXACTLY // Modu belirle, üst Görünüm, specSize tarafından belirlenen alt Görünüm boyutunun belirlenmesini umar; MeasureSpec.AT_MOST // Maksimum mod, üst Görünüm, alt Görünüm boyutunun en fazla specSize tarafından belirtilen değerde olmasını umar; MeasureSpec.UNSPECIFIED // Hiçbir mod belirtilmez, üst Görünüm tamamen alt View öğesinin tasarım değerine göre belirlenir;

3. View'un ölçüm yöntemi nihaidir ve aşırı yüklemeye izin verilmez View alt sınıfları, ölçüm mantığını tamamlamak için yalnızca onMeasure üzerinde aşırı yükleme yapabilir.

4. Üst DecorView ölçülürken MeasureSpec, ViewRootImpl'deki getRootMeasureSpec yöntemi tarafından belirlenir (LayoutParams genişlik ve yükseklik parametreleri MATCH_PARENT, specMode TAM ve specSize fiziksel ekran boyutudur).

5. ViewGroup sınıfı, parent-child View öğesinin boyutunun hesaplanmasını basitleştirmek için measureChild, measureChild ve measureChildWithMargins yöntemlerini sağlar.

6. ViewGroup'un bir alt sınıfı olduğu sürece, LayoutParams'ın MarginLayoutParams'ı devralması gerekir, aksi takdirde layout_margin parametresi kullanılamaz.

7. Görünümün düzen boyutu, ana Görünüm ve alt Görünüm tarafından ortaklaşa belirlenir.

8. View'ün ölçülen genişliği ve yüksekliğini elde etmek için View'un getMeasuredWidth () ve getMeasuredHeight () yöntemlerini kullanın.Geçerli bir değer döndürmek için bu iki yöntemin onMeasure işleminden sonra çağrıldığından emin olmalısınız.

Sınırlı alan nedeniyle, iki sonuç daha var, bunu bir sonraki alt ifadeye koyacağız, "Android'de Görünüm'ün çalışma prensibinin derinlemesine analizi (Bölüm 2)" bölümüne göz atmaya hoş geldiniz.

Son olarak, herkesin bir öğrenme yönü olması için editör tarafından derlenen Android ile ilgili öğrenme zihin haritasını ekleyin.

Android gelişmiş

En son Android teknolojisi

Flutter

Mobil mimar

Bu Android öğrenim materyallerine ve mülakat materyallerine ihtiyaç duyanların ilgiye ihtiyacı var + özel mesaj "Android materyallerini" ücretsiz olarak yanıtlıyor!

Grupta, ileri düzey kullanıcı arayüzü, performans optimizasyonu, mimar kursları, NDK, hibrit geliştirme: ReactNative + Weex ve diğer Android teknik bilgi mimarisi video materyalleri ve kariyer planlaması dahil olmak üzere üst düzey Android hakkında birçok ücretsiz öğrenme materyali bulunmaktadır. Ve görüşme rehberliği.

Switch'in şeffaf kabuğu biraz güçlü görünüyor
önceki
Jingdong Buzdolabını Keşfetmek: İçerisi ile dışarısı arasındaki fark, soğuk kış aylarında çalışma ve sıcak yaz aylarında ısıtma ile 50 derecedirTitanyum Medya Videosu "Çevrimiçi"
Sonraki
Model oyun kontrolü: eski Gao Gao Jing Baofan
WeChat'in hacmi azaltması gerekiyor mu?
Bugün birkaç eski film nostaljisini tavsiye edeceğim
Model oyun kontrolü: İki boyutlu boyama ile SD Neo Zeon
Lezzet Okuma | Şiir, ışık ve gölge buluşuyor, kazanıyor ama sayısız yaratıcılık
Flutter Teknik Araştırma Raporu
Model oyun kontrolü: şehir evi fetişi Red Cancan Yeni Zeon
Flutter bileşeni öğrenme (iki) -Görüntü
Model oyun kontrolü: 2018 ortak HG00 Seraph
Zero Zero Infinite Ürün Yöneticisi: Hover Camera nasıl doğdu ve hava dronlarından farkı
Üç yüz gün boyunca perakende stratejisini zorlayan Tencent, "kağıtları teslim ediyor" | Titanyum Medya Derinliği
Flutter bileşeni öğrenimi (1) - Metin bileşeni
To Top