Görünümün getWidth () ve getHeight () işlevi 0 değerini döndürür


513

Android projemdeki tüm öğeleri dinamik olarak oluşturuyorum. Bir düğmenin genişliğini ve yüksekliğini almaya çalışıyorum, böylece düğmeyi etrafında döndürebilirim. Ben sadece android diliyle çalışmayı öğrenmeye çalışıyorum. Ancak 0 döndürür.

Biraz araştırma yaptım ve onCreate()yöntemden başka bir yerde yapılması gerektiğini gördüm . Birisi bana bunun nasıl yapılacağına dair bir örnek verebilirse, bu harika olurdu.

İşte benim geçerli kod:

package com.animation;

import android.app.Activity;
import android.os.Bundle;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.Button;
import android.widget.LinearLayout;

public class AnimateScreen extends Activity {


//Called when the activity is first created.
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    LinearLayout ll = new LinearLayout(this);

    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(30, 20, 30, 0);

    Button bt = new Button(this);
    bt.setText(String.valueOf(bt.getWidth()));

    RotateAnimation ra = new RotateAnimation(0,360,bt.getWidth() / 2,bt.getHeight() / 2);
    ra.setDuration(3000L);
    ra.setRepeatMode(Animation.RESTART);
    ra.setRepeatCount(Animation.INFINITE);
    ra.setInterpolator(new LinearInterpolator());

    bt.startAnimation(ra);

    ll.addView(bt,layoutParams);

    setContentView(ll);
}

Herhangi bir yardım takdir.

Yanıtlar:


197

Aradığınız getWidth()çok erken. Kullanıcı arayüzü henüz boyutlandırılmadı ve ekranda gösterilmedi.

Yaptığınız şeyi yapmak istediğinizden şüpheliyim - yine de - animasyonlu widget'lar tıklanabilir alanlarını değiştirmez ve bu nedenle düğme, nasıl döndürüldüğüne bakılmaksızın orijinal yönündeki tıklamalara yanıt verir.

Bununla birlikte , düğme boyutunu tanımlamak için bir boyut kaynağı kullanabilir, ardından bu sorunu önlemek için bu boyut kaynağına mizanpaj dosyanızdan ve kaynak kodunuzdan başvurabilirsiniz .


82
ngreenwood6, diğer çözümünüz nedir?
Andrew

12
@Andrew - negreenwood6'nın takipinizden haberdar edilmesini istiyorsanız, mesajınızı size yaptığım gibi başlatmanız gerekir (ilk üç harfin yeterli olduğunu düşünüyorum) - CommonsWare bu yanıtı yazdığı için otomatik olarak bildirilir, ancak ngreen değil onlara hitap etmedikçe.
Peter Ajtai

11
@WindowFocusChanged (boolean hasFocus) üzerinde genel boşluğu geçersiz kıl {// TODO Otomatik oluşturulan yöntem saplama super.onWindowFocusChanged (hasFocus); // Burada boyutu alabilirsiniz! }
Sana

5
@ ngreenwood6 Peki çözümünüz neydi?
Nima Vaziri

5
Ekranınız hazır olduğunda boyut almak için bu dinleyiciyi kullanın. view.getViewTreeObserver (). addOnGlobalLayoutListener (yeni ViewTreeObserver.OnGlobalLayoutListener () {}
Sinan Dizdarević

815

Temel sorun, gerçek ölçümler için çizim aşamasını beklemeniz gerektiğidir (özellikle wrap_contentveya gibi dinamik değerlerle match_parent), ancak genellikle bu aşama tamamlanmamıştır onResume(). Bu nedenle bu aşamayı beklemek için bir geçici çözüme ihtiyacınız var. Bunun farklı olası çözümleri vardır:


1. Çizim / Mizanpaj Etkinliklerini Dinleyin: ViewTreeObserver

Farklı çizim etkinlikleri için bir ViewTreeObserver tetiklenir. Genellikle OnGlobalLayoutListenerölçümü almak için istediğiniz şeydir, bu yüzden dinleyicideki kod düzen aşamasından sonra çağrılır, böylece ölçümler hazırdır:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                view.getHeight(); //height is ready
            }
        });

Not: Dinleyici hemen kaldırılacaktır, aksi takdirde her düzen olayında tetiklenecektir. SDK Lvl <16 uygulamalarını desteklemeniz gerekiyorsa, dinleyicinin kaydını silmek için bunu kullanın:

public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)


2. Düzen kuyruğuna bir runnable ekleyin: View.post ()

Çok iyi bilinmeyen ve en sevdiğim çözüm. Temel olarak, Görünüm'ün yayın yöntemini kendi runnable'ınızla kullanın. Bu temelde kodunuzu kuyruklar sonra da belirttiği gibi vb görünümün ölçü, düzen, Romain Guy :

UI olay kuyruğu olayları sırayla işler. SetContentView () çağrıldıktan sonra, olay kuyruğunda geçiş izni isteyen bir ileti bulunur, bu nedenle kuyruğa gönderdiğiniz her şey mizanpaj geçişinden sonra gerçekleşir

Misal:

final View view=//smth;
...
view.post(new Runnable() {
            @Override
            public void run() {
                view.getHeight(); //height is ready
            }
        });

Avantajı ViewTreeObserver:

  • kodunuz yalnızca bir kez yürütülür ve bir güçlük olabilen yürütme işleminden sonra Gözlemciyi devre dışı bırakmanız gerekmez
  • daha az ayrıntılı sözdizimi

Referanslar:


3. Görünümlerin onLayout Yönteminin Üzerine Yaz

Bu sadece mantığın görünümde kapsüllenebileceği belirli durumlarda pratiktir, aksi takdirde bu oldukça ayrıntılı ve hantal bir sözdizimidir.

view = new View(this) {
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        view.getHeight(); //height is ready
    }
};

Ayrıca, onLayout'un birçok kez çağrılacağını unutmayın, bu nedenle yöntemde ne yaptığınızı düşünün veya ilk kez kodunuzu devre dışı bırakın


4. Düzen aşamasından geçip geçmediğini kontrol edin

Kullanıcı arabirimini oluştururken birden çok kez yürütülen kodunuz varsa, aşağıdaki destek v4 lib yöntemini kullanabilirsiniz:

View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
   viewYouNeedHeightFrom.getHeight();
}

Görünüm, bir pencereye en son eklendiğinden veya pencereden ayrıldığından beri en az bir mizanpajdan geçtiğinde true değerini döndürür.


Ek: Statik olarak tanımlanmış ölçümler alma

Sadece statik olarak tanımlanmış yükseklik / genişlik elde etmek yeterliyse, bunu aşağıdakilerle yapabilirsiniz:

Ancak, bunun çizimden sonra gerçek genişlik / yükseklikten farklı olabileceğini unutmayın. Javadoc farkı mükemmel bir şekilde anlatıyor:

Bir görünümün boyutu, genişlik ve yükseklik ile ifade edilir. Bir görünüm aslında iki çift genişlik ve yükseklik değerine sahiptir.

İlk çift ölçülen genişlik ve ölçülen yükseklik olarak bilinir. Bu boyutlar, bir görünümün üst öğesi içinde ne kadar büyük olmasını istediğini tanımlar (daha fazla ayrıntı için Düzen'e bakın.) Ölçülen boyutlar, getMeasuredWidth () ve getMeasuredHeight () çağrılarak elde edilebilir.

İkinci çift basitçe genişlik ve yükseklik veya bazen çizim genişliği ve çizim yüksekliği olarak bilinir. Bu boyutlar ekrandaki görünümün, çizim zamanında ve mizanpajdan sonra gerçek boyutunu tanımlar. Bu değerler ölçülen genişlik ve yükseklikten farklı olabilir, ancak zorunlu değildir. Genişlik ve yükseklik getWidth () ve getHeight () çağrılarak elde edilebilir.


1
View.post'u bir parçada kullanıyorum. Ebeveyninin yüksekliği 0dp olan ve bir ağırlık seti olan görünümü ölçmem gerekiyor. Denediğim her şey sıfır yüksekliğini döndürüyor (global düzen dinleyicisini de denedim). View.post kodunu 125'de bir gecikmeyle onViewCreated'e koyduğumda çalışır, ancak gecikme olmadan çalışır. Fikirler?
Psest328

6
çok kötü bir cevap sadece bir kez vurabilir, gerçekten iyi bir açıklama. Uygulamamdan çıkıp son uygulamalardan yeniden başlatırsam görünüm döndürmeyle ilgili bir sorun yaşadım. Oradan kaydırdı ve taze bir yeniden başlatma yaptı, her şey iyiydi. View.post () günümü kurtardı!
Matthias

1
Teşekkürler. Genellikle View.post () (2. yöntem) kullanın, ancak bir arka plan iş parçacığından çağrılırsa bazen çalışmaz. Yani, yazmayı unutma runOnUiThread(new Runnable() {...}. Şu anda 1 yöntemin bir varyasyonu kullanın: addOnLayoutChangeListener. İçeride çıkarmayı unutmayın: removeOnLayoutChangeListener. Arka plan iş parçacığından da çalışır.
CoolMind

2
Ne yazık ki benim için 'View.post ()' her zaman çalışmıyor. Çok benzer 2 projem var. Bir projede çalışıyor, diğerinde çalışmıyor. . :( Her iki durumda ben 'onCreate ()' an Etkinliğinizden yönteminden yöntemini çağırın Herhangi öneri neden.?
Adrian Buciuman

1
Sorunu buldum. Bir projede görünüm, android:visibility="gone"diğer projede görünürlük özelliği vardı invisible. Görünürlük gonegenişlik her zaman 0 olacaktır.
Adrian Buciuman

262

Kullanabiliriz

@Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  //Here you can get the size!
 }

10
Peki Parçalar'da nasıl çalışabiliriz? bu sadece Faaliyet'te mi çalışır?
Olkunmustafa

8
Bu bir şey yapmalı ama cevap olmamalı. Bir kullanıcı, pencereye çizmek yerine herhangi bir zamanda boyut almak ister. Ayrıca bu yöntem, pencerede bir değişiklik (Görünüm gizle, gitti, yeni görünümler ekleme vb.) Olduğunda her seferinde birden çok kez çağrılır. Dikkatli kullanın :)
Dr. aNdRO

1
Bu bir hack ve ViewTreeObserver kullanmak daha iyi olurdu gibi görünüyor.
i_am_jorf

Kulağa kaba gelmek istemeyin, ancak otomatik olarak oluşturulan bir yorum bırakmak (özellikle 2 satır satırda) aslında ne yaptığınızı bildiğinize çok fazla güvenmez.
Natix

Neyse ki benim için işe yaramadı. ViewTreeObserver'ı denemek benim için çalıştı.
Narendra Singh

109

Ben onWindowFocusChanged () daha iyi olduğunu düşünüyorum bu çözümü kullandım. Bir DialogFragment'ı açar ve telefonu döndürürseniz, onWindowFocusChanged yalnızca kullanıcı iletişim kutusunu kapattığında çağrılır):

    yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            // Ensure you call it only once :
            yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

            // Here you can get the size :)
        }
    });

Düzenleme: removeGlobalOnLayoutListener kullanımdan kaldırıldığı için şimdi şunları yapmanız gerekir:

@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {

    // Ensure you call it only once :
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
        yourView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }
    else {
        yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }

    // Here you can get the size :)
}

4
bu en iyi çözüm! çünkü genişlik ve yüksekliğin bilinmediği ve hesaplanmadığı dinamik çözümlerde de çalışır. örneğin, diğer öğelerin konumlarına / boyutlarına dayalı göreceli bir düzende. Aferin!
George Pligoropoulos

2
Bu, API 16 veya üstü (Jelly bean) gerektirir
tomsv

7
API 16 GEREKTİRMEZ! Tek sorun API 16'dan kaldırılan yöntem removeGlobalOnLayoutListener olmakla basit tüm API düzeyleri ile uyumlu bir çözüm yoktur - stackoverflow.com/questions/16189525/...
Gingo

Dinleyiciyi birçok kez çağırmayacak şekilde kaldırmanız gerekiyorsa ve farklı API'larla ilgili sorun yaşıyorsanız, sınıfın başında yanlış bir boole ayarlamanız ve ardından değerleri yakaladıktan sonra true olarak ayarlamanız önerilir. değerleri tekrar yakalamaz.
Mansour Fahad

İlk çalıştırmadan sonra dinleyiciyi kaldırırsanız yanlış boyutlar alabilirsiniz: 02-14 10: 18: 53.786 15063-15063 / PuzzleFragment: height: 1647 width: 996 02-14 10: 18: 53.985 15063-15063 / Puzzle : 1500 genişlik: 996
Leos Literak

76

Ian'ın bu Android Geliştiricileri iş parçacığında belirttiği gibi :

Her neyse, anlaşma, bir pencerenin içeriğinin düzeninin, tüm öğeler oluşturulduktan ve üst görünümlerine eklendikten sonra gerçekleşmesidir . Bu şekilde olmalı, çünkü bir Görünümün hangi bileşenleri içerdiğini ve neleri içerdiğini ve benzeri şeyleri öğrenene kadar, onu yerleştirmenin mantıklı bir yolu yoktur.

Alt satırda, bir kurucuda getWidth () vb. Olarak çağırırsanız, sıfır döndürür. Yordam, yapıcıdaki tüm görünüm öğelerinizi oluşturmak, sonra Görünümünüzün onSizeChanged () yönteminin çağrılmasını beklemektir - bu, gerçek boyutunuzu ilk kez bulduğunuzda, GUI öğelerinizin boyutlarını ayarladığınız zamandır.

Ayrıca onSizeChanged () işlevinin bazen sıfır parametreleriyle çağrıldığına dikkat edin - bu durumu kontrol edin ve hemen geri dönün (böylece düzeninizi hesaplarken sıfıra bölünmezsiniz). Bir süre sonra gerçek değerlerle çağrılır.


8
onSizeChanged benim için çalıştı. onWindowFocusedChanged özel görünümümde çağrılmıyor.
aaronmarino

Bu iyi bir çözüm gibi görünüyor, ancak her zaman onSizeChanged yöntemini geçersiz kılmak için özel görünümümü oluşturmalıyım?
M.ElSaka

Bottom line, if you call getWidth() etc. in a constructor, it will return zero.bu bir tombala. Sorunlarımın kökü.
MikeNereson

Bu çözümü parçalar için öneriyorum, çünkü diğer bazı çözümler benim durumumda 0 döndürüyor.
WindRider

66

Ekranda görüntülenmeden önce bazı widget'ların genişliğini almanız gerekiyorsa, getMeasuredWidth () veya getMeasuredHeight () kullanabilirsiniz.

myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();

5
Nedense benim için çalışan tek cevap buydu - teşekkürler!
Farbod Salamat-Zadeh

Aynı şekilde, benim için çalışan en basit çözüm
Tima

Bir cazibe gibi çalışın!
Morales Batovski

1
@CommonsWare bu çözüm global düzenler geri aramasında görünüm ağacı gözlemcisinden önce yükseklik ve genişlik nasıl verir, bir açıklama yardımcı olacaktır.
Mightian

22

Diğer dinleyicilerden biraz daha erken çağrıldığından OnPreDrawListener()bunun yerine kullanmayı tercih ederim addOnGlobalLayoutListener().

    view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
    {
        @Override
        public boolean onPreDraw()
        {
            if (view.getViewTreeObserver().isAlive())
                view.getViewTreeObserver().removeOnPreDrawListener(this);

            // put your code here
            return true;
        }
    });

3
Not return falsevasıtaları için iptal akım çekme geçişi . Bunun return trueyerine ilerlemek için .
Pang

9

Global düzende gözlemlemek ve yükseklik dinamik olarak hazır olduğunda belirli bir görevi yerine getirmek için bir Kotlin Uzantısı.

Kullanımı:

view.height { Log.i("Info", "Here is your height:" + it) }

Uygulama:

fun <T : View> T.height(function: (Int) -> Unit) {
    if (height == 0)
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                function(height)
            }
        })
    else function(height)
}

6

AndroidX, bu tür işlerde size yardımcı olan birden fazla uzantı işlevine sahiptir. androidx.core.view

Bunun için Kotlin'i kullanmanız gerekir.

Burada en uygun olanı doOnLayout:

Bu görünüm ortaya konulduğunda verilen eylemi gerçekleştirir. Görünüm düzenlenmişse ve bir düzen istenmediyse, eylem hemen gerçekleştirilir, aksi takdirde görünüm bir sonraki düzenlendikten sonra eylem gerçekleştirilir.

Eylem bir sonraki mizanpajda yalnızca bir kez çağrılır ve ardından kaldırılır.

Örneğinizde:

bt.doOnLayout {
    val ra = RotateAnimation(0,360,it.width / 2,it.height / 2)
    // more code
}

Bağımlılık: androidx.core:core-ktx:1.0.0



1
bu uzantılar gerçekten önemli ve yararlı
Ahmed na

5

RxJava & RxBindings kullanıyorsanız bir astar . Isıtıcı plaka olmadan benzer yaklaşım. Bu aynı zamanda, Tim Autin'in cevabındaki gibi uyarıları bastırmak için hack'i de çözer .

RxView.layoutChanges(yourView).take(1)
      .subscribe(aVoid -> {
           // width and height have been calculated here
      });

Budur. Hiç çağrılmasa bile abonelikten çıkmanıza gerek yoktur.


4

Bunun nedeni, görünümün şişirilmesi için daha fazla zamana ihtiyaç duymasıdır. Bu nedenle, çağırmak view.widthve view.heightana iş parçacığı yerine , zaten şişirilmiş view.post { ... }olduğundan emin olmak için kullanmalısınız view. Kotlin'de:

view.post{width}
view.post{height}

Java'da ayrıca a yöntemlerini arayabilir getWidth()ve to yöntemini iletebilirsiniz .getHeight()RunnableRunnableview.post()

view.post(new Runnable() {
        @Override
        public void run() {
            view.getWidth(); 
            view.getHeight();
        }
    });

Gönderilen kod yürütüldüğünde görünüm ölçülemeyip düzenlenemeyebileceğinden bu doğru yanıt değildir. Bu bir uç durum olurdu, ancak .postgörünümün ortaya konacağını garanti etmez.
d.aemon

1
Bu cevap harikaydı ve benim için çalıştı. Çok teşekkür ederim Ehsan
MMG

3

Yükseklik ve genişlik sıfırdır, çünkü görünüm yükseklik ve genişlik istediğiniz zamana göre oluşturulmamıştır. En basit çözümlerden biri

view.post(new Runnable() {
        @Override
        public void run() {
            view.getHeight(); //height is ready
            view.getWidth(); //width is ready
        }
    });

Bu yöntem, kısa ve net olduğu için diğer yöntemlere kıyasla iyidir.


3

Belki bu birine yardımcı olur:

View sınıfı için bir uzantı işlevi oluşturma

dosyaadı: ViewExt.kt

fun View.afterLayout(what: () -> Unit) {
    if(isLaidOut) {
        what.invoke()
    } else {
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                what.invoke()
            }
        })
    }
}

Bu daha sonra aşağıdakilerle herhangi bir görünümde kullanılabilir:

view.afterLayout {
    do something with view.height
}

2

İle cevap postyanlıştır, çünkü boyut yeniden hesaplanmamış olabilir.
Bir diğer önemli şey, görüşün ve tüm atalarının görünür olması gerektiğidir . Bunun için bir özellik kullanıyorum View.isShown.

İşte benim kotlin fonksiyonu, bu utils bir yere yerleştirilebilir:

fun View.onInitialized(onInit: () -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (isShown) {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                onInit()
            }
        }
    })
}

Ve kullanımı:

myView.onInitialized {
    Log.d(TAG, "width is: " + myView.width)
}

1

Kotlin kullanıyorsanız

  leftPanel.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {

            override fun onGlobalLayout() {

                if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                    leftPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
                }
                else {
                    leftPanel.viewTreeObserver.removeGlobalOnLayoutListener(this)
                }

                // Here you can get the size :)
                leftThreshold = leftPanel.width
            }
        })

0

Gone görünümleri, uygulama arka planda ise yükseklik olarak 0 değerini döndürür. Bu kodum (% 1oo çalışır)

fun View.postWithTreeObserver(postJob: (View, Int, Int) -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            val widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            measure(widthSpec, heightSpec)
            postJob(this@postWithTreeObserver, measuredWidth, measuredHeight)
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                @Suppress("DEPRECATION")
                viewTreeObserver.removeGlobalOnLayoutListener(this)
            } else {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        }
    })
}

0

Görünümün çizilmesi için beklememiz gerekiyor. Bu amaçla OnPreDrawListener kullanın. Kotlin örneği:

val preDrawListener = object : ViewTreeObserver.OnPreDrawListener {

                override fun onPreDraw(): Boolean {
                    view.viewTreeObserver.removeOnPreDrawListener(this)

                    // code which requires view size parameters

                    return true
                }
            }

            view.viewTreeObserver.addOnPreDrawListener(preDrawListener)
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.