Jak odzyskać wymiary widoku?


209

Mam pogląd złożony z TableLayout, TableRow and TextView. Chcę, żeby to wyglądało jak siatka. Muszę uzyskać wysokość i szerokość tej siatki. Metody getHeight()i getWidth()zawsze zwracają 0. Dzieje się tak, gdy dynamicznie formatuję siatkę, a także gdy używam wersji XML.

Jak odzyskać wymiary widoku?


Oto mój program testowy, którego użyłem w Debugowaniu do sprawdzenia wyników:

import android.app.Activity;
import android.os.Bundle;
import android.widget.TableLayout;
import android.widget.TextView;

public class appwig extends Activity {  
    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.maindemo);  //<- includes the grid called "board"
      int vh = 0;   
      int vw = 0;

      //Test-1 used the xml layout (which is displayed on the screen):
      TableLayout tl = (TableLayout) findViewById(R.id.board);  
      tl = (TableLayout) findViewById(R.id.board);
      vh = tl.getHeight();     //<- getHeight returned 0, Why?  
      vw = tl.getWidth();     //<- getWidth returned 0, Why?   

      //Test-2 used a simple dynamically generated view:        
      TextView tv = new TextView(this);
      tv.setHeight(20);
      tv.setWidth(20);
      vh = tv.getHeight();    //<- getHeight returned 0, Why?       
      vw = tv.getWidth();    //<- getWidth returned 0, Why?

    } //eof method
} //eof class

zamiast używać getWidth / Height użyj getMeasuredWidth / Height po zastosowaniu układu do działania.
bhups

9
Chłopaki, wszystko co trzeba zrobić, to zadzwonić, getHeight()a getWidth()po zakończeniu cyklu życia Aktywności poprosił opinie, żeby się zmierzyły, innymi słowy, robią tego rodzaju rzeczy onResume()i to wszystko. Nie należy oczekiwać, że jeszcze nie rozplanowany obiekt pozna jego wymiary, to cała sztuczka. Nie ma potrzeby używania magii sugerowanej poniżej.
układarka klas

2
Wywołanie getHeight()/Width()w onResume()nie dały> 0 wartość dla mnie.
Darpan

@ClassStacker Co z fragmentem?
zdd

@zdd Zadaj nowe pytanie i zamieść tutaj odniesienie w komentarzu.
układarka klas

Odpowiedzi:


418

Wierzę, że PO już dawno minęło, ale na wypadek gdyby ta odpowiedź była w stanie pomóc przyszłym poszukiwaczom, pomyślałem, że opublikuję znalezione rozwiązanie. Dodałem ten kod do mojej onCreate()metody:

ZMIENIONO: 07.05.11, aby dołączyć kod z komentarzy:

final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

    @Override
    public void onGlobalLayout() {
        LayerDrawable ld = (LayerDrawable)tv.getBackground();
        ld.setLayerInset(1, 0, tv.getHeight() / 2, 0, 0);
        ViewTreeObserver obs = tv.getViewTreeObserver();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            obs.removeOnGlobalLayoutListener(this);
        } else {
            obs.removeGlobalOnLayoutListener(this);
        }
    }

});

Najpierw otrzymuję ostateczne odniesienie do mojego TextView(aby uzyskać dostęp w onGlobalLayout()metodzie). Następnie pobieram ViewTreeObserverod siebie TextViewi dodam OnGlobalLayoutListenerprzesłonięcie onGLobalLayout(wydaje się , że nie ma tu metody nadklasy do wywołania tutaj ...) i dodanie mojego kodu, który wymaga znajomości pomiarów widoku dla tego odbiornika. Wszystko działa zgodnie z oczekiwaniami, więc mam nadzieję, że to pomoże.


23
@George Bailey: Jedną z rzeczy, które ostatnio widziałem, jak ktoś inny opublikował (sam tego nie testowałem, więc YMMV) w celu przezwyciężenia wielu wywołań było (w ramach onGlobalLayout ()): w tv.getViewTreeObserver().removeGlobalOnLayoutListener(this);ten sposób słuchacz powinien zostać usunięty po raz pierwszy Pojawia się.
Kevin Coppock,

3
Też chcę pomóc. Może być konieczne wykonanie czegoś takiego: mView.getViewTreeObserver (). RemoveGlobalOnLayoutListener (globalLayoutListener); jeśli chcesz zmienić rozmiar swojego widoku tylko raz. Mam nadzieję, że ta pomoc
Henry Sou,

7
Pytanie jest bardzo stare, ale korzystanie z nich również addOnLayoutChangeListenerdziała! :)
FX

7
Zauważ też, że od API 16 potrzebujesz: if (android.os.Build.VERSION.SDK_INT> = 16) {view.getViewTreeObserver (). RemoveOnGlobalLayoutListener (this); } else {view.getViewTreeObserver (). removeGlobalOnLayoutListener (this); }
Edison,

2
Ponieważ removeGlobalOnLayoutListener jest przestarzałe, nadal wyświetla ostrzeżenia w środowisku Eclipse. Czy to normalne? Z czasem przestarzałe ostrzeżenia o kodach zalałyby cały świat ...
Jonny,

82

Dodam tylko alternatywne rozwiązanie, zastąpię metodę onWindowFocusChanged twojej aktywności, a stamtąd będziesz mógł uzyskać wartości getHeight (), getWidth ().

@Override
public void onWindowFocusChanged (boolean hasFocus) {
        // the height will be set at this point
        int height = myEverySoTallView.getMeasuredHeight(); 
}

4
Cytując dokumentację: „Jest to najlepszy wskaźnik tego, czy ta aktywność jest widoczna dla użytkownika”.
Cameron Lowell Palmer,

6
To nie działa w przypadku fragmentów, których musisz użyć ViewTreeObserver.
Mihir,

OP konkretnie zapytał, jak to zrobić dla widoku
JustDanyul,

scrollview całkowita wysokość i maks. Scrollview.getScrolly () będzie taki sam? tj. W moim przypadku mam Wysokość z powyższą metodą to 702 i otrzymałem maksymalną wartość z getScrolly () to 523,
Hitesh Dhamshaniya

Ta metoda nie działa również, gdy Twój widok jest dołączany z innego pliku xml.
Jay Donga,

19

Próbujesz uzyskać szerokość i wysokość elementów, które nie zostały jeszcze narysowane.

Jeśli użyjesz debugowania i zatrzymasz się w pewnym momencie, zobaczysz, że ekran Twojego urządzenia jest nadal pusty, to dlatego, że twoje elementy nie zostały jeszcze narysowane, więc nie możesz uzyskać szerokości i wysokości czegoś, co jeszcze nie istnieć.

I mogę się mylić, ale setWidth()nie zawsze jestem szanowany, Layoutokreśla, że ​​są to dzieci i decyduje, jak je mierzyć (dzwonić child.measure()), więc jeśli ustawiszsetWidth() , nie będziesz mieć gwarancji, że uzyskasz tę szerokość po narysowaniu elementu.

Czego potrzebujesz, to użyć getMeasuredWidth() (najnowszej miary swojego widoku) gdzieś po tym, jak widok został narysowany.

Zbadać Activity cykl życia, aby znaleźć najlepszy moment.

http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle

Uważam, że dobrą praktyką jest używanie w OnGlobalLayoutListenerten sposób:

yourView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (!mMeasured) {
                // Here your view is already layed out and measured for the first time
                mMeasured = true; // Some optional flag to mark, that we already got the sizes
            }
        }
    });

Możesz umieścić ten kod bezpośrednio w nim onCreate(), a zostanie on wywołany, gdy zostaną utworzone widoki.


Niestety nie działa również w OnResume. Ostateczne wymiary są ustawiane gdzieś po wywołaniu OnResume, przynajmniej w emulatorze.
jki

To okropna praktyka !!! Ten słuchacz będzie wzywany w kółko i zabije twój występ. Zamiast używać flag, wyrejestruj nasłuchiwanie po zakończeniu.
bluewhile

15

Skorzystaj z metody postu Widoku w ten sposób

post(new Runnable() {   
    @Override
    public void run() {
        Log.d(TAG, "width " + MyView.this.getMeasuredWidth());
        }
    });

1
To działa, ale tak naprawdę nie ma dyskusji dlaczego? Btw, działa to, ponieważ uruchamialny nie uruchamia się, dopóki nie wystąpi układ.
Norman H

7

Próbowałem użyć onGlobalLayout()niestandardowego formatowania a TextView, ale jak zauważył @George Bailey, onGlobalLayout()tak naprawdę jest on wywoływany dwukrotnie: raz na początkowej ścieżce układu i drugi raz po zmodyfikowaniu tekstu.

View.onSizeChanged()działa dla mnie lepiej, ponieważ jeśli tam zmodyfikuję tekst, metoda jest wywoływana tylko raz (podczas przejścia układu). Wymagało to podklasy TextView, ale na poziomie API 11+View. addOnLayoutChangeListener() można użyć, aby uniknąć podklasy.

Jeszcze jedna rzecz, aby uzyskać poprawną szerokość widoku View.onSizeChanged(), layout_widthnależy ustawić na match_parentnie wrap_content.


2

Czy próbujesz uzyskać rozmiary w konstruktorze, czy jakąkolwiek inną metodę, która jest uruchamiana PRZED uzyskaniem rzeczywistego obrazu?

Nie otrzymasz żadnych wymiarów, zanim wszystkie składniki nie zostaną zmierzone (ponieważ twój plik xml nie wie o twoim rozmiarze wyświetlacza, pozycjach rodziców i tym podobnych)

Spróbuj uzyskać wartości po onSizeChanged () (choć można go wywołać zerem) lub po prostu poczekaj, aż otrzymasz rzeczywisty obraz.


Zaktualizowałem moje oryginalne pytanie i kod, aby były bardziej przejrzyste. Dziękuję Ci.

2

Jak wspomniano FX, możesz użyć OnLayoutChangeListenerwidoku, który chcesz śledzić

view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        // Make changes
    }
});

Możesz usunąć detektor w wywołaniu zwrotnym, jeśli chcesz tylko początkowy układ.



1

ViewTreeObserver i onWindowFocusChanged() wcale nie są tak potrzebne.

Jeśli nadmuchasz TextViewukład jako i / lub umieścisz w nim trochę treści i ustawisz LayoutParams, możesz użyć getMeasuredHeight()igetMeasuredWidth() .

ALE musisz być ostrożny zLinearLayouts (może także innymi ViewGroups). Problem polega na tym, że możesz uzyskać szerokość i wysokość później, onWindowFocusChanged()ale jeśli spróbujesz dodać w niej niektóre widoki, nie możesz uzyskać tych informacji, dopóki wszystko nie zostanie narysowane. Próbowałem dodać wielokrotność, TextViewsaby LinearLayoutsnaśladować FlowLayout(styl owijania), więc nie mogłem użyć Listeners. Po uruchomieniu proces powinien być kontynuowany synchronicznie. W takim przypadku możesz chcieć zachować szerokość w zmiennej, aby użyć jej później, ponieważ podczas dodawania widoków do układu możesz jej potrzebować.


1

Mimo że proponowane rozwiązanie działa, może nie być najlepszym rozwiązaniem dla każdego przypadku, ponieważ jest oparte na dokumentacji ViewTreeObserver.OnGlobalLayoutListener

Definicja interfejsu dla wywołania zwrotnego, które ma być wywoływane, gdy zmienia się stan układu globalnego lub widoczność widoków w drzewie widoku.

co oznacza, że ​​jest wywoływany wiele razy i nie zawsze mierzony jest widok (ma określoną wysokość i szerokość)

Alternatywą jest użycie, ViewTreeObserver.OnPreDrawListenerktóre wywoływane jest tylko wtedy, gdy widok jest gotowy do narysowania i ma wszystkie swoje pomiary.

final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnPreDrawListener(new OnPreDrawListener() {

    @Override
    public void onPreDraw() {
        tv.getViewTreeObserver().removeOnPreDrawListener(this);
        // Your view will have valid height and width at this point
        tv.getHeight();
        tv.getWidth();
    }

});


0

Możesz użyć transmisji, która jest wywoływana w OnResume ()

Na przykład:

int vh = 0;   
int vw = 0;

public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.maindemo);  //<- includes the grid called "board"

      registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                TableLayout tl = (TableLayout) findViewById(R.id.board);  
                tl = (TableLayout) findViewById(R.id.board);
                vh = tl.getHeight();       
                vw = tl.getWidth();               
            }
      }, new IntentFilter("Test"));
}

protected void onResume() {
        super.onResume();

        Intent it = new Intent("Test");
        sendBroadcast(it);

}

Nie można uzyskać wysokości widoku w OnCreate (), onStart (), a nawet w onResume () z tego powodu, że odpowiedział kcoppock


0

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.


-1

Prosta odpowiedź: To działało dla mnie bez problemu. Wydaje się, że kluczem jest upewnienie się, że widok ma fokus przed uzyskaniem getHeight itp. Zrób to za pomocą metody hasFocus (), a następnie za pomocą metody getHeight () w tej kolejności. Wymagane są tylko 3 wiersze kodu.

ImageButton myImageButton1 =(ImageButton)findViewById(R.id.imageButton1); myImageButton1.hasFocus();

int myButtonHeight = myImageButton1.getHeight();

Log.d("Button Height: ", ""+myButtonHeight );//Not required

Mam nadzieję, że to pomoże.



-8

KOREKTA: Dowiedziałem się, że powyższe rozwiązanie jest okropne. Zwłaszcza, gdy Twój telefon jest wolny. I tutaj znalazłem inne rozwiązanie: oblicz wartość px elementu, w tym marginesów i wypełnień: dp do px: https://stackoverflow.com/a/6327095/1982712

lub dimens.xml na px: https://stackoverflow.com/a/16276351/1982712

sp do px: https://stackoverflow.com/a/9219417/1982712 (odwróć rozwiązanie)

lub dimens do px: https://stackoverflow.com/a/16276351/1982712

i to wszystko.


10
Zwykle złym pomysłem jest mieć nadzieję, że wszystko zostanie ustawione poprawnie po dowolnej arbitralnie wybranej liczbie milisekund. Urządzenie może być powolna, innych procesów / wątków może być uruchomiony, może nie działać w debugger, mogą nie działać w następnej wersji systemu operacyjnego ...
Kristopher Johnson
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.