Wyświetl getWidth () i getHeight () zwraca 0

513

Dynamicznie tworzę wszystkie elementy mojego projektu na Androida. Próbuję uzyskać szerokość i wysokość przycisku, aby móc go obrócić. Próbuję tylko nauczyć się, jak pracować z językiem Androida. Zwraca jednak 0.

Przeprowadziłem badania i zauważyłem, że należy to zrobić gdzieś indziej niż w onCreate()metodzie. Gdyby ktoś mógł podać mi przykład, jak to zrobić, byłoby świetnie.

Oto mój obecny 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);
}

Każda pomoc jest mile widziana.

ngreenwood6
źródło

Odpowiedzi:

197

Dzwonisz getWidth()za wcześnie. Interfejs użytkownika nie został jeszcze dopasowany i rozmieszczony na ekranie.

Wątpię, czy chcesz robić to, co robisz - animowane widżety nie zmieniają ich klikalnych obszarów, więc przycisk nadal będzie reagował na kliknięcia w pierwotnej orientacji, niezależnie od tego, jak się obrócił.

To powiedziawszy, możesz użyć zasobu wymiaru, aby zdefiniować rozmiar przycisku, a następnie odwołać się do tego zasobu wymiaru z pliku układu i kodu źródłowego, aby uniknąć tego problemu.

CommonsWare
źródło
82
ngreenwood6, jakie jest twoje inne rozwiązanie?
Andrew
12
@Andrew - jeśli chcesz, aby negreenwood6 był powiadamiany o twoich działaniach następczych, musisz rozpocząć wiadomość tak, jak ja ci to zrobiłem (myślę, że pierwsze trzy litery są wystarczające) - CommonsWare otrzymuje powiadomienie automatycznie, ponieważ napisał tę odpowiedź, ale ngreen nie robi chyba, że ​​się do nich zwrócisz.
Peter Ajtai,
11
@Override public void onWindowFocusChanged (boolean hasFocus) {// TODO Metoda wygenerowana automatycznie super.onWindowFocusChanged (hasFocus); // Tutaj możesz uzyskać rozmiar! }
Sana,
5
@ ngreenwood6 Więc jakie było twoje rozwiązanie?
Nima Vaziri
5
Użyj tego detektora, aby uzyskać rozmiar, kiedy ekran będzie gotowy. view.getViewTreeObserver (). addOnGlobalLayoutListener (nowy ViewTreeObserver.OnGlobalLayoutListener () {}
Sinan Dizdarević
815

Podstawowym problemem jest to, że trzeba czekać na fazę rysowania dla rzeczywistych pomiarów (szczególnie z wartościami dynamicznymi, takimi jak wrap_contentlub match_parent), ale zazwyczaj ta faza nie została jeszcze ukończona onResume(). Potrzebujesz obejścia, aby poczekać na tę fazę. Istnieją różne możliwe rozwiązania tego:


1. Słuchaj zdarzeń losowania / układu: ViewTreeObserver

ViewTreeObserver zostaje zwolniony dla różnych zdarzeń rysowania. Zwykle OnGlobalLayoutListenerjest to, co chcesz uzyskać pomiar, więc kod w detektorze zostanie wywołany po fazie układu, więc pomiary są gotowe:

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

Uwaga: Detektor zostanie natychmiast usunięty, ponieważ w przeciwnym razie będzie uruchamiany przy każdym zdarzeniu układu. Jeśli musisz obsługiwać aplikacje SDK Lvl <16, użyj tego do wyrejestrowania nasłuchującego:

public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)


2. Dodaj plik wykonywalny do kolejki układu: View.post ()

Niezbyt dobrze znane i moje ulubione rozwiązanie. Zasadniczo po prostu użyj metody postu View z własnym runnable. To w zasadzie umieszcza kod w kolejce po takcie widoku, układzie itp., Jak stwierdził Romain Guy :

Kolejka zdarzeń interfejsu użytkownika będzie przetwarzać zdarzenia w kolejności. Po wywołaniu metody setContentView () kolejka zdarzeń będzie zawierać komunikat z prośbą o przekazanie, więc wszystko, co opublikujesz w kolejce, nastąpi po przejściu układu

Przykład:

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

Przewaga nad ViewTreeObserver:

  • Twój kod jest wykonywany tylko raz i nie musisz wyłączać Obserwatora po wykonaniu, co może być kłopotliwe
  • mniej pełna składnia

Bibliografia:


3. Nadpisz metodę ViewLayout

Jest to praktyczne tylko w niektórych sytuacjach, gdy logikę można zamknąć w samym widoku, w przeciwnym razie jest to dość pełna i nieporęczna składnia.

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
    }
};

Pamiętaj też, że onLayout będzie wywoływany wiele razy, więc zastanów się, co robisz w metodzie, lub wyłącz swój kod po raz pierwszy


4. Sprawdź, czy przeszedł fazę układu

Jeśli masz kod, który wykonuje się wiele razy podczas tworzenia interfejsu użytkownika, możesz użyć następującej metody wsparcia v4 lib:

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

Zwraca wartość true, jeśli widok przeszedł przez co najmniej jeden układ od czasu ostatniego przyłączenia lub odłączenia od okna.


Dodatkowo: uzyskiwanie statycznie zdefiniowanych pomiarów

Jeśli wystarczy uzyskać statycznie zdefiniowaną wysokość / szerokość, możesz to zrobić za pomocą:

Pamiętaj jednak, że po rysowaniu może się ona różnić od rzeczywistej szerokości / wysokości. Jawadok doskonale opisuje różnicę:

Rozmiar widoku jest wyrażany za pomocą szerokości i wysokości. Widok faktycznie zawiera dwie pary wartości szerokości i wysokości.

Pierwsza para jest znana jako zmierzona szerokość i zmierzona wysokość. Wymiary te określają, jak duży widok ma być w swoim obiekcie nadrzędnym (zobacz Układ, aby uzyskać więcej informacji.) Zmierzone wymiary można uzyskać, wywołując getMeasuredWidth () i getMeasuredHeight ().

Druga para jest po prostu znana jako szerokość i wysokość, lub czasami szerokość i wysokość rysunku. Wymiary te określają rzeczywisty rozmiar widoku na ekranie, w czasie rysowania i po układzie. Wartości te mogą, ale nie muszą, różnić się od zmierzonej szerokości i wysokości. Szerokość i wysokość można uzyskać, wywołując getWidth () i getHeight ().

Patrick Favre
źródło
1
Używam view.post we fragmencie. Muszę zmierzyć widok, którego rodzic ma wysokość 0dp i ma ustawiony ciężar. Wszystko, co próbuję, zwraca wysokość zero (próbowałem również globalnego detektora układu). Kiedy wstawiam kod view.post w onViewCreated z opóźnieniem 125, działa, ale nie bez opóźnienia. Pomysły?
Psest328
6
szkoda, że ​​możesz głosować odpowiedź tylko raz, naprawdę dobre wytłumaczenie. Miałem problem z rotacją widoku, jeśli zamknąłem aplikację i uruchomiłem ją ponownie z najnowszych aplikacji. Jeśli wyciągnąłem go stamtąd i ponownie uruchomiłem, wszystko było w porządku. View.post () uratował mi dzień!
Matthias
1
Dzięki. Zwykle używam View.post () (2. metoda), ale jeśli jest wywoływany z wątku w tle, czasami nie działa. Więc nie zapomnij napisać runOnUiThread(new Runnable() {...}. Obecnie używam odmianę metody 1: addOnLayoutChangeListener. Nie zapomnij usunąć go następnie w środku: removeOnLayoutChangeListener. Działa również z wątku w tle.
CoolMind
2
Niestety dla mnie „View.post ()” nie działa przez cały czas. Mam 2 projekty, które są bardzo podobne. W jednym projekcie działa, w drugim nie działa. :(. W obu sytuacjach wywołuję metodę z metody „onCreate ()” z działania. Wszelkie sugestie dlaczego?
Adrian Buciuman
1
Znajduję problem. W jednym projekcie widok ma android:visibility="gone", aw drugim projekcie była właściwość widoczności invisible. Jeśli widoczność jest widoczna, goneszerokość będzie zawsze wynosić 0.
Adrian Buciuman
262

Możemy użyć

@Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  //Here you can get the size!
 }
Sana
źródło
10
więc jak możemy pracować w Fragmentach? to działa tylko w działaniu?
Olkunmustafa
8
To powinno wystarczyć, ale nie powinna być odpowiedzią. Użytkownik chce uzyskać wymiary w dowolnym momencie zamiast rysować w oknie. Również ta metoda jest wywoływana wielokrotnie lub za każdym razem, gdy pojawi się zmiana (Zobacz ukrywanie, znikanie, dodawanie nowych widoków itp.) W oknie. Więc używaj go ostrożnie :)
Dr aNdRO,
1
To jest hack i wygląda na to, że użycie ViewTreeObserver byłoby lepsze.
i_am_jorf,
Nie chcę brzmieć niegrzecznie, ale pozostawienie automatycznie wygenerowanego komentarza (szczególnie w 2 liniach kodu) nie daje pewności, że faktycznie wiesz, co robisz.
Natix
Niestety, nie zadziałało to dla mnie. Próbowanie viewTreeObserver zadziałało dla mnie.
Narendra Singh,
109

Użyłem tego rozwiązania, które moim zdaniem jest lepsze niż onWindowFocusChanged (). Jeśli otworzysz DialogFragment, a następnie obrócisz telefon, onWindowFocusChanged zostanie wywołany tylko wtedy, gdy użytkownik zamknie okno dialogowe):

    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 :)
        }
    });

Edycja: ponieważ removeGlobalOnLayoutListener jest przestarzałe, powinieneś teraz:

@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 :)
}
Tim Autin
źródło
4
jest to zdecydowanie najlepsze rozwiązanie! ponieważ działa również w przypadku rozwiązań dynamicznych, w których szerokość i wysokość nie są znane i obliczone. tj. we względnym układzie opartym na pozycjach / rozmiarach innych elementów. Dobra robota!
George Pligoropoulos
2
Wymaga to API 16 lub wyższej (Jelly Bean)
tomsv
7
NIE WYMAGA API 16! Jedynym problemem jest metoda removeGlobalOnLayoutListener wycofana z API 16, ale istnieje proste rozwiązanie kompatybilne ze wszystkimi poziomami interfejsu API - stackoverflow.com/questions/16189525/...
gingo
Jeśli musisz usunąć detektor, aby nie wywoływał się wiele razy i masz problemy z różnymi interfejsami API, zalecam ustawienie fałszywego logicznego na początku klasy, a następnie po przechwyceniu wartości ustaw wartość true, aby nie przechwytuje ponownie wartości.
Mansour Fahad,
Jeśli usuniesz słuchacza po pierwszym uruchomieniu, możesz mieć złe wymiary: 02-14 10: 18: 53,786 15063-15063 / PuzzleFragment: wysokość: 1647 szerokość: 996 02-14 10: 18: 53.985 15063-15063 / PuzzleFragment: wysokość : 1500 szerokość: 996
Leos Literak
76

Jak stwierdza Ian w tym wątku dla programistów Androida :

W każdym razie umowa polega na tym, że układ zawartości okna ma miejsce po zbudowaniu wszystkich elementów i dodaniu ich do widoków nadrzędnych. Tak musi być, ponieważ dopóki nie dowiesz się, jakie składniki zawiera Widok i co one zawierają, itd., Nie ma rozsądnego sposobu na jego rozłożenie.

Podsumowując, jeśli wywołasz getWidth () itp. W konstruktorze, zwróci zero. Procedura polega na utworzeniu wszystkich elementów widoku w konstruktorze, a następnie odczekaniu na wywołanie metody onSizeChanged () w widoku - wtedy po raz pierwszy dowiesz się, jaki jest Twój rzeczywisty rozmiar, a więc wtedy ustawisz rozmiary elementów GUI.

Pamiętaj też, że onSizeChanged () jest czasami wywoływany z parametrami zerowymi - sprawdź w tym przypadku i wróć natychmiast (aby nie otrzymać podziału przez zero podczas obliczania układu itp.). Jakiś czas później zostanie wywołany z rzeczywistymi wartościami.

kerem yokuva
źródło
8
onSizeChanged pracował dla mnie. onWindowFocusedChanged nie jest wywoływany w moim widoku niestandardowym.
aaronmarino
wygląda to na dobre rozwiązanie, ale co powinienem zawsze tworzyć mój własny widok, aby zastąpić metodę onSizeChanged?
M.ElSaka
Bottom line, if you call getWidth() etc. in a constructor, it will return zero.to bingo. Źródło moich problemów.
MikeNereson
Proponuję to rozwiązanie dla fragmentów, ponieważ niektóre inne rozwiązania zwracają w moim przypadku 0.
WindRider
66

Jeśli chcesz uzyskać szerokość jakiegoś widżetu, zanim zostanie on wyświetlony na ekranie, możesz użyć getMeasuredWidth () lub getMeasuredHeight ().

myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();
sulu.dev
źródło
5
Z jakiegoś powodu była to jedyna odpowiedź, która zadziałała dla mnie - dzięki!
Farbod Salamat-Zadeh
To samo dla mnie, najprostsze rozwiązanie, które mi zadziałało
Tima
Pracuj jak urok!
Morales Batovski
1
@CommonsWare, w jaki sposób to rozwiązanie podaje wysokość i szerokość przed obserwatorem drzewa widoku w wywołaniu zwrotnym układów globalnych, pomocne byłoby wyjaśnienie.
Mightian
22

Wolałbym używać OnPreDrawListener()zamiast addOnGlobalLayoutListener(), ponieważ nazywa się to nieco wcześniej niż inni słuchacze.

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

            // put your code here
            return true;
        }
    });
Ayaz Alifov
źródło
3
Zauważ, że return falseoznacza to anulowanie aktualnego przebiegu rysowania . return trueZamiast tego kontynuować .
Pang
9

Rozszerzenie Kotlin do obserwacji globalnego układu i wykonywania określonego zadania, gdy wysokość jest dynamicznie gotowa.

Stosowanie:

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

Realizacja:

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)
}
Renetik
źródło
6

AndroidX ma wiele funkcji rozszerzenia, które pomagają w tego rodzaju pracy wewnątrz androidx.core.view

W tym celu musisz użyć Kotlina.

Ten, który najlepiej tu pasuje, to doOnLayout:

Wykonuje podaną akcję, gdy ten widok jest ułożony. Jeśli widok został ułożony i nie zażądał układu, akcja zostanie wykonana natychmiast, w przeciwnym razie akcja zostanie wykonana po następnym ułożeniu widoku.

Akcja zostanie wywołana tylko raz na następnym układzie, a następnie usunięta.

W twoim przykładzie:

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

Zależność: androidx.core:core-ktx:1.0.0

Vlad
źródło
To jedyna poprawna odpowiedź. android.googlesource.com/platform/frameworks/support/+/…
hrules6872
1
rozszerzenia te są naprawdę ważne i przydatne
Ahmed na
5

Jedna wkładka, jeśli używasz RxJava i RxBindings . Podobne podejście bez płyty kotłowej. To także rozwiązuje problem hakowania, aby ukryć ostrzeżenia, jak w odpowiedzi Tima Autina .

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

To jest to. Nie musisz rezygnować z subskrypcji, nawet jeśli nigdy nie dzwonisz.

Odys
źródło
4

Dzieje się tak, ponieważ widok potrzebuje więcej czasu na zawyżenie. Więc zamiast dzwonić view.widthi view.heightw głównym wątku, powinieneś użyć, view.post { ... }aby upewnić się, że twój viewzostał już napompowany. W Kotlinie:

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

W Javie można również zadzwonić getWidth()i getHeight()metod w Runnablei przekazać Runnabledo view.post()metody.

view.post(new Runnable() {
        @Override
        public void run() {
            view.getWidth(); 
            view.getHeight();
        }
    });
Ehsan Nokandi
źródło
To nie jest poprawna odpowiedź, ponieważ widok może nie zostać zmierzony i ułożony podczas wykonywania opublikowanego kodu. Byłby to przypadek na krawędzi, ale .postnie gwarantuje widoku.
Demon
1
Ta odpowiedź była świetna i działała dla mnie. Dziękuję bardzo Ehsan
MMG
3

Wysokość i szerokość wynoszą zero, ponieważ widok nie został utworzony do momentu, gdy zażądasz jego wysokości i szerokości. Jednym z najprostszych rozwiązań jest

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

Ta metoda jest dobra w porównaniu do innych metod, ponieważ jest krótka i rześka.

Shivam Yadav
źródło
3

Może to pomaga komuś:

Utwórz funkcję rozszerzenia dla klasy View

nazwa pliku: 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()
            }
        })
    }
}

Można go następnie użyć w dowolnym widoku z:

view.afterLayout {
    do something with view.height
}
Pepijn
źródło
2

Odpowiedź z postjest nieprawidłowa, ponieważ rozmiar może nie zostać ponownie obliczony.
Inną ważną rzeczą jest to, że widok i wszyscy jego przodkowie muszą być widoczni . Do tego używam nieruchomości View.isShown.

Oto moja funkcja kotlin, którą można umieścić gdzieś w utils:

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

A użycie to:

myView.onInitialized {
    Log.d(TAG, "width is: " + myView.width)
}
pavelperc
źródło
1

Jeśli używasz Kotlin

  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
            }
        })
Hitesh Sahu
źródło
0

Brak wyświetleń zwraca 0 jako wysokość, jeśli aplikacja działa w tle. Ten mój kod (1oo% działa)

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)
            }
        }
    })
}

źródło
0

Musimy poczekać, aż widok zostanie narysowany. W tym celu użyj OnPreDrawListener. Przykład Kotlina:

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)
Artem Botnev
źródło