Bir mizanpajın ne zaman çizildiğini nasıl anlarsınız?


201

Ekrana kaydırılabilir bir bitmap çizen özel bir görünüm var. Başlamak için, üst düzen nesnesinin piksel cinsinden boyutunu iletmem gerekiyor. Ancak onCreate ve onResume işlevleri sırasında, Düzen henüz çizilmedi ve bu nedenle layout.getMeasuredHeight () işlevi 0 değerini döndürür.

Geçici bir çözüm olarak, bir saniye beklemek ve sonra ölçmek için bir işleyici ekledim. Bu işe yarıyor, ama özensiz, ve düzen çizilmeden önce bitmeden önce ne kadar süre kesebileceğimi bilmiyorum.

Bilmek istediğim, bir mizanpaj çizildiğinde nasıl tespit edebilirim? Bir olay veya geri arama var mı?

Yanıtlar:


391

Düzene bir ağaç gözlemcisi ekleyebilirsiniz. Bu, doğru genişlik ve yüksekliği döndürmelidir. onCreate()alt görünümlerinin düzeni yapılmadan önce çağrılır. Yani genişlik ve yükseklik henüz hesaplanmamıştır. Yüksekliği ve genişliği elde etmek için, onCreate()yöntemi uygulayın:

    final LinearLayout layout = (LinearLayout) findViewById(R.id.YOUR_VIEW_ID);
    ViewTreeObserver vto = layout.getViewTreeObserver(); 
    vto.addOnGlobalLayoutListener (new OnGlobalLayoutListener() { 
        @Override 
        public void onGlobalLayout() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    layout.getViewTreeObserver()
                            .removeOnGlobalLayoutListener(this);
                } else {
                    layout.getViewTreeObserver()
                            .removeGlobalOnLayoutListener(this);
                }
            int width  = layout.getMeasuredWidth();
            int height = layout.getMeasuredHeight(); 

        } 
    });

28
Çok teşekkür ederim! Ama bunu yapmak için neden daha basit bir yöntem olmadığını merak ediyorum çünkü bu yaygın bir sorundur.
felixd

1
İnanılmaz. OnLayoutChangeListener'ı denedim ve işe yaramadı. Ancak OnGlobalLayoutListener çalıştı. Çok teşekkür ederim!
YoYoMyo

8
removeGlobalOnLayoutListener API 16 düzeyinde kullanımdan kaldırılmıştır. Bunun yerine removeOnGlobalLayoutListener öğesini kullanın.
tounaobun

3
Gördüğüm bir "gotcha" görüşünüz görünür değilse, onGlobalLayout çağrılır, ancak daha sonra görünür olduğunda getView çağrılır, ancak onGlobalLayout ... ugh değil.
Stephen McCormick

1
Bunun için başka bir hızlı çözüm kullanmaktır view.post(Runnable), daha temiz ve aynı şeyi yapar.
dvdciri

74

Gerçekten kolay bir yol, sadece post()düzeninizi çağırmaktır . Bu, görünümünüzü oluşturduktan sonraki kodunuzu bir sonraki adımda çalıştıracaktır.

YOUR_LAYOUT.post( new Runnable() {
    @Override
    public void run() {
        int width  = YOUR_LAYOUT.getMeasuredWidth();
        int height = YOUR_LAYOUT.getMeasuredHeight(); 
    }
});

4
Görünüm zaten oluşturulduktan sonra yazı aslında kodunuzu çalıştıracak mı bilmiyorum. Gönderi, yazı oluşturduğunuz Runnable'ı UQ İş parçacığında önceki kuyruktan sonra yürütülecek RunQueue'ya ekleyeceğinden garanti yoktur.
HendraWD

4
Aksine, post (), görünüm oluşturulduktan sonra gerçekleşen bir sonraki kullanıcı arayüzü adım güncellemesinden sonra yürütülür. Bu nedenle, görünüm yapıldıktan sonra yürütüleceği garanti edilir.
Elliptica

Bu kodu birkaç kez test ettim, sadece UI iş parçacığında en iyi şekilde çalışır. Bir arka plan iş parçacığında bazen çalışmaz.
CoolMind

3
@HendraWijayaDjiono, belki bu yazı cevaplayacaktır: stackoverflow.com/questions/21937224/…
CoolMind

1
İlk cevabı kullanmanın her seferinde işe yaramadığı, kaydırmam gereken görünümü (kaydırma görünümü) kullanarak, görünümü almaya hazır olur olmaz istenen konuma doğru kaydırmamı sağlayan bir sorunla karşılaştım. scrollTo yöntemi çağrıları.
Danuofr

22

Kullanımdan kaldırılmış kod ve uyarıları önlemek için şunları kullanabilirsiniz:

view.getViewTreeObserver().addOnGlobalLayoutListener(
        new ViewTreeObserver.OnGlobalLayoutListener() {
            @SuppressWarnings("deprecation")
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    view.getViewTreeObserver()
                            .removeOnGlobalLayoutListener(this);
                } else {
                    view.getViewTreeObserver()
                            .removeGlobalOnLayoutListener(this);
                }
                yourFunctionHere();
            }
        });

1
Bu kodu kullanabilmek için 'görünüm', 'son' olarak bildirilmelidir.
BeccaP

2
Bu durumu yerine Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN kullanmak
HendraWD

4

Kotlin uzatma fonksiyonları ile daha iyi

inline fun View.waitForLayout(crossinline yourAction: () -> Unit) {
    val vto = viewTreeObserver
    vto.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            when {
                vto.isAlive -> {
                    vto.removeOnGlobalLayoutListener(this)
                    yourAction()
                }
                else -> viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        }
    })
}

Yeniden kullanılabilen çok güzel ve zarif bir çözüm.
Ionut Negru

3

Başka bir cevap:
Boyutları görüntüle adresinde kontrol etmeyi deneyin onWindowFocusChanged.


3

Genel yöntemlere bir alternatif, görünümün çizimine kanca yapmaktır.

OnPreDrawListenerbir görünüm görüntülenirken birçok kez çağrılır, bu nedenle görünümünüzün geçerli ölçülen genişliğe veya yüksekliğe sahip olduğu belirli bir yineleme yoktur. Bunun için sıfırdan büyük view.getMeasuredWidth() <= 0olup olmadığını sürekli olarak doğrulamanız ( ) veya bir sınır belirlemeniz gerekir measuredWidth.

Ayrıca görünümün asla çizilmemesi ihtimali de vardır, bu da kodunuzdaki diğer sorunları gösterebilir.

final View view = [ACQUIRE REFERENCE]; // Must be declared final for inner class
ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        if (view.getMeasuredWidth() > 0) {     
            view.getViewTreeObserver().removeOnPreDrawListener(this);
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
            //Do something with width and height here!
        }
        return true; // Continue with the draw pass, as not to stop it
    }
});

Sorun şu How can you tell when a layout has been drawn?ki, bir yöntemi çağırdığınız bir yöntem sağlıyorsunuz OnPreDrawListenerve don't stop interrogating that view until it has provided you with a reasonable answerböylece çözümünüze yapılan itirazlar açık olmalıdır.
Terk Edilmiş Araba

Bilmeniz gereken şey çekildiği zaman değil, ölçüldüğü zamandır. Kodum gerçekten sorulması gereken soruya cevap veriyor.
jwehrle

Bu çözümle ilgili bir sorun mu var? Karşılaşılan sorunu çözemez mi?
jwehrle

1
Görünüm, yerleştirilene kadar ölçülmez, bu nedenle bu alternatif aşırıdır ve fazladan bir kontrol gerçekleştirir. Soruyu ele almadığınızı itiraf ediyorsunuz, ancak ne hissettiğiniz sorulmalıdır. Orijinal yorum bu noktaların her ikisini de yaptı, ancak kodun kendisinde de düzenleme olarak gönderilen bazı sorunlar var.
Terk Edilmiş Araba

Güzel bir düzenleme. Teşekkür ederim. Sanırım sorduğum soru, sizce bu cevabın silinip silinmeyeceğini düşünüyor musunuz? OP'nin sorunuyla karşılaştım. Bu sorunu çözdü. İyi niyetle teklif ettim. Bu bir hata mıydı? Bu StackOverflow üzerinde yapılmaması gereken bir şey mi?
jwehrle

2

Düzeninizdeki veya görünümünüzdeki yayınlama yöntemini çağırarak kontrol edin

 view.post( new Runnable() {
     @Override
     public void run() {
        // your layout is now drawn completely , use it here.
      }
});

Bu doğru değildir ve olası sorunları gizleyebilir. postrunnable'ı çalıştırmak için sıraya alacak, görünümün ölçülmesini, hatta daha az çizilmesini sağlamaz.
Ionut Negru

@IonutNegru belki bu okuma stackoverflow.com/a/21938380
Bruno Bieri

@BrunoBieri, bu davranışı görünüm ölçümlerini değiştiren async işlevleriyle test edebilir ve sıraya alınan görevinizin her hesaplamadan sonra çalıştırılıp çalıştırılmadığını ve beklenen değerleri almasını görebilirsiniz.
Ionut Negru

1

Göründüğünde onMeasuregörünüm ölçülen genişliğini / yüksekliğini alır. Bundan sonra arayabilirsiniz layout.getMeasuredHeight().


4
OnMeasure tetiklendiğinde bilmek için kendi özel görünümünüzü oluşturmak zorunda kalmaz mıydınız?
Geeks On Hugs

Evet, ölçüme erişmek için özel görünümler kullanmanız gerekir.
Trevor Hart
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.