Określanie rozmiaru widoku systemu Android w czasie wykonywania

123

Próbuję zastosować animację do widoku w mojej aplikacji na Androida po utworzeniu mojej aktywności. Aby to zrobić, muszę określić bieżący rozmiar widoku, a następnie skonfigurować animację, aby skalować od bieżącego rozmiaru do nowego rozmiaru. Ta część musi być wykonana w czasie wykonywania, ponieważ widok skaluje się do różnych rozmiarów w zależności od danych wprowadzonych przez użytkownika. Mój układ jest zdefiniowany w XML.

Wydaje się, że jest to łatwe zadanie i jest wiele pytań SO dotyczących tego, ale żadne z nich oczywiście nie rozwiązało mojego problemu. Więc może brakuje mi czegoś oczywistego. Uzyskuję uchwyt do mojego widoku poprzez:

ImageView myView = (ImageView) getWindow().findViewById(R.id.MyViewID);

Działa to dobrze, ale podczas rozmowy getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().width, itd., Wszystkie one powrót 0. Próbowałem również ręcznie dzwoniąc measure()na widoku, a następnie wywołaniu getMeasuredWidth(), ale to nie ma znaczenia.

Próbowałem wywołać te metody i sprawdzić obiekt w debugerze w mojej aktywności onCreate()i w onPostCreate(). Jak mogę określić dokładne wymiary tego widoku w czasie wykonywania?

Nik Reiman
źródło
1
Aha, również powinienem zauważyć, że sam widok zdecydowanie nie ma 0 szerokości / wysokości. Na ekranie pojawia się dobrze.
Nik Reiman
Czy możesz opublikować tag <ImageView ... /> z układu XML?
fhucho
Zmagałem się z wieloma rozwiązaniami SO dla tego problemu, dopóki nie zdałem sobie sprawy, że w moim przypadku wymiary Widoku pasują do fizycznego ekranu (moja aplikacja jest „immersyjna”, a Widok i wszystkie jego nadrzędne szerokości i wysokości są ustawione na match_parent). W tym przypadku prostszym rozwiązaniem, które można bezpiecznie wywołać nawet przed narysowaniem widoku (np. Z metody onCreate () Twojego działania) jest po prostu użycie Display.getSize (); Szczegółowe informacje można znaleźć na stackoverflow.com/a/30929599/5025060 . Wiem, że nie o to prosił @NikReiman, po prostu zostawiając notatkę tym, którzy mogą być w stanie użyć tej prostszej metody.
CODE-READ

Odpowiedzi:

180

Użyj ViewTreeObserver w widoku, aby poczekać na pierwszy układ. Dopiero po pierwszym układzie getWidth () / getHeight () / getMeasuredWidth () / getMeasuredHeight () zadziała.

ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
  viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
      view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
      viewWidth = view.getWidth();
      viewHeight = view.getHeight();
    }
  });
}
Vikram Bodicherla
źródło
21
Uważaj na tę odpowiedź, ponieważ będzie ona stale nazywana, jeśli podstawowym widokiem jest wideo / powierzchnia
Kevin Parker
7
Czy możesz rozwinąć ten temat @Kevin? Przede wszystkim dwie rzeczy, ponieważ jest to słuchacz, spodziewałbym się jednego oddzwonienia, a następnie, gdy tylko otrzymamy pierwszy oddzwonienie, usuwamy słuchacza. Przegapiłem coś?
Vikram Bodicherla
4
Przed wywołaniem jakiejkolwiek metody ViewTreeObserver zaleca się sprawdzenie isAlive (): if (view.getViewTreeObserver (). IsAlive ()) {view.getViewTreeObserver (). RemoveGlobalOnLayoutListener (this); }
Peter Tran
4
jakakolwiek alternatywa dla removeGlobalOnLayoutListener(this);? ponieważ jest przestarzały.
Sibbs Gambling,
7
@FarticlePilter, zamiast tego użyj removeOnGlobalLayoutListener (). Jest to wymienione w dokumentach.
Vikram Bodicherla
64

W zależności od scenariusza istnieje wiele rozwiązań:

  1. Bezpieczna metoda zadziała tuż przed narysowaniem widoku, po zakończeniu fazy projektowania:
public static void runJustBeforeBeingDrawn(final View view, final Runnable runnable) {
    final OnPreDrawListener preDrawListener = new OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            view.getViewTreeObserver().removeOnPreDrawListener(this);
            runnable.run();
            return true;
        }
    };
    view.getViewTreeObserver().addOnPreDrawListener(preDrawListener); 
}

Przykładowe użycie:

    ViewUtil.runJustBeforeBeingDrawn(yourView, new Runnable() {
        @Override
        public void run() {
            //Here you can safely get the view size (use "getWidth" and "getHeight"), and do whatever you wish with it
        }
    });
  1. W niektórych przypadkach wystarczy ręcznie zmierzyć rozmiar widoku:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth(); 
int height=view.getMeasuredHeight();

Jeśli znasz rozmiar pojemnika:

    val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxWidth, View.MeasureSpec.AT_MOST)
    val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST)
    view.measure(widthMeasureSpec, heightMeasureSpec)
    val width=view.measuredWidth
    val height=view.measuredHeight
  1. jeśli masz rozszerzony widok niestandardowy, możesz uzyskać jego rozmiar metodą „onMeasure”, ale myślę, że działa ona dobrze tylko w niektórych przypadkach:
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    final int newHeight= MeasureSpec.getSize(heightMeasureSpec);
    final int newWidth= MeasureSpec.getSize(widthMeasureSpec);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
  1. Jeśli piszesz w Kotlinie, możesz skorzystać z kolejnej funkcji, która za kulisami działa dokładnie tak runJustBeforeBeingDrawn, jak napisałem:

    view.doOnPreDraw { actionToBeTriggered() }

    Zauważ, że musisz dodać to do gradle (znajdziesz tutaj ):

    implementation 'androidx.core:core-ktx:#.#'
programista Androida
źródło
1
Wydaje się, że rozwiązanie nr 1 nie zawsze będzie działać OnPreDrawListener. Testowany przypadek, gdy chcesz uruchomić kod z Activity onCreate()i od razu uruchomisz aplikację w tle. Łatwe do powtórzenia, szczególnie na wolniejszym urządzeniu. Jeśli zastąpi OnPreDrawListenersię OnGlobalLayoutListener- nie będzie widać ten sam problem.
pytający
@questioner Nie rozumiem, ale cieszę się, że w końcu Ci pomogło.
programista Androida
Chociaż zwykle używam ViewTreeObserverlub post, w moim przypadku numer 2 pomógł ( view.measure).
CoolMind,
3
@CoolMind Możesz także użyć nowej metody, jeśli używasz Kotlin. Zaktualizowana odpowiedź, aby ją również uwzględnić.
programista Androida
dlaczego kotlin ma do tego dodatkowe funkcje? google są szalone, ten język jest bezużyteczny (powinien pozostać z java), jest znacznie starszy niż szybki, ale tylko szybki ma wystarczającą popularność tiobe.com/tiobe-index (swift 12 pozycja i kotlin 38)
user924
18

Czy dzwonisz, getWidth()zanim widok zostanie faktycznie przedstawiony na ekranie?

Częstym błędem popełnianym przez nowych programistów Androida jest używanie szerokości i wysokości widoku wewnątrz jego konstruktora. Gdy wywoływany jest konstruktor widoku, system Android nie wie jeszcze, jak duży będzie widok, więc rozmiary są ustawione na zero. Rzeczywiste rozmiary są obliczane na etapie układu, który ma miejsce po zakończeniu budowy, ale przed rysowaniem. Możesz użyć onSizeChanged()metody, aby otrzymać powiadomienie o wartościach, gdy są one znane, lub możesz użyć metod getWidth()i getHeight()później, na przykład w onDraw()metodzie.

Mark B.
źródło
2
Czy to oznacza, że ​​muszę zastąpić ImageView tylko po to, aby złapać onSizeChange()zdarzenie, aby poznać rzeczywisty rozmiar? Wydaje się, że to trochę przesada ... chociaż w tym momencie desperacko pragnę, aby kod działał, więc warto spróbować.
Nik Reiman
Myślałem raczej o czekaniu, aż onCreate () zakończy działanie.
Mark B
1
Jak wspomniano, próbowałem umieścić ten kod, onPostCreate()który jest uruchamiany po całkowitym utworzeniu i uruchomieniu widoku.
Nik Reiman,
1
Skąd pochodzi cytowany tekst?
Intrications
1
ten cytat dla niestandardowego widoku, nie publikuj go za taką odpowiedź, gdy pytamy o rozmiar widoku w ogóle
user924
17

Na podstawie porady @ mbaird znalazłem działające rozwiązanie, podklasując ImageViewklasę i nadpisując onLayout(). Następnie stworzyłem interfejs obserwatora, który zaimplementował moje działanie i przekazałem odniesienie do siebie do klasy, co pozwoliło jej powiedzieć, kiedy działanie zostało zakończone.

Nie jestem w 100% przekonany, że to najlepsze rozwiązanie (stąd jeszcze nie zaznaczam tej odpowiedzi jako poprawnej), ale działa i według dokumentacji jest to pierwszy raz, kiedy można znaleźć rzeczywisty rozmiar widoku.

Nik Reiman
źródło
Dobrze wiedzieć. Jeśli znajdę cokolwiek, co pozwoliłoby ci ominąć podklasę widoku, opublikuję to tutaj. Jestem trochę zaskoczony, że funkcja PostCreate () nie działa.
Mark B
Cóż, próbowałem tego i wydaje się, że działa. Po prostu nie jest to najlepsze rozwiązanie, ponieważ ta metoda jest wywoływana często, a nie tylko raz na początku. :(
Tobias Reich,
10

Oto kod do pobierania układu poprzez przesłanianie widoku, jeśli API <11 (API 11 zawiera funkcję View.OnLayoutChangedListener):

public class CustomListView extends ListView
{
    private OnLayoutChangedListener layoutChangedListener;

    public CustomListView(Context context)
    {
        super(context);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        if (layoutChangedListener != null)
        {
            layoutChangedListener.onLayout(changed, l, t, r, b);
        }
        super.onLayout(changed, l, t, r, b);
    }

    public void setLayoutChangedListener(
        OnLayoutChangedListener layoutChangedListener)
    {
        this.layoutChangedListener = layoutChangedListener;
    }
}
public interface OnLayoutChangedListener
{
    void onLayout(boolean changed, int l, int t, int r, int b);
}
gregm
źródło
6

Możesz sprawdzić to pytanie . Można użyć View„s post () metody.

Macarse
źródło
5

Użyj poniższego kodu, podaje rozmiar widoku.

@Override
public void onWindowFocusChanged(boolean hasFocus) {
       super.onWindowFocusChanged(hasFocus);
       Log.e("WIDTH",""+view.getWidth());
       Log.e("HEIGHT",""+view.getHeight());
}
Asif Patel
źródło
5

To działa dla mnie w moim onClickListener:

yourView.postDelayed(new Runnable() {               
    @Override
    public void run() {         
        yourView.invalidate();
        System.out.println("Height yourView: " + yourView.getHeight());
        System.out.println("Width yourView: " + yourView.getWidth());               
    }
}, 1);
Biro Csaba Norbert
źródło
4
Nie musisz publikować opóźnionych, gdy znajduje się w odbiorniku kliknięć. Nie możesz nic kliknąć, dopóki wszystkie widoki się nie zmierzą i nie pojawią się na ekranie. W ten sposób zostaną już zmierzone do czasu, gdy będziesz w stanie je kliknąć.
afollestad
Ten kod jest nieprawidłowy. Opóźnienie postu nie gwarantuje, że w następnym tiku widok zostanie zmierzony.
Ian Wong
Polecam nie iść na postDelayed (). Prawdopodobnie tak się nie stanie, ale co, jeśli widok nie zostanie zmierzony w ciągu 1 sekundy i wywołasz to działanie, aby uzyskać szerokość / wysokość widoku. Nadal dałoby to 0. Edycja: Aha i przy okazji, że 1 jest w milisekundach, więc na pewno nie powiedzie się.
Alex
4

Byłem też stracił około getMeasuredWidth()i getMeasuredHeight() getHeight()i getWidth()przez długi czas .......... później okazało się, że coraz szerokość widoku i wysokość w onSizeChanged()jest najlepszym sposobem, aby to zrobić ........ można dynamicznie uzyskać BIEŻĄCĄ szerokość i BIEŻĄCĄ wysokość swojego widoku, zastępująconSizeChanged( metodę).

może zechcesz rzucić okiem na ten, który zawiera rozbudowany fragment kodu. Nowy post na blogu: jak uzyskać wymiary szerokości i wysokości niestandardowego widoku (rozszerza widok) w systemie Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html

Rakib
źródło
0

Na ekranie można wyświetlić zarówno pozycję, jak i wymiar widoku

 val viewTreeObserver: ViewTreeObserver = videoView.viewTreeObserver;

if (viewTreeObserver.isAlive) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            //Remove Listener
            videoView.viewTreeObserver.removeOnGlobalLayoutListener(this);

            //View Dimentions
            viewWidth = videoView.width;
            viewHeight = videoView.height;

            //View Location
            val point = IntArray(2)
            videoView.post {
                videoView.getLocationOnScreen(point) // or getLocationInWindow(point)
                viewPositionX = point[0]
                viewPositionY = point[1]
            }

        }
    });
}
Hitesh Sahu
źródło
-1

u mnie działa perfekcyjnie:

 protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
        CTEditor ctEdit = Element as CTEditor;
        if (ctEdit == null) return;           
        if (e.PropertyName == "Text")
        {
            double xHeight = Element.Height;
            double aHaight = Control.Height;
            double height;                
            Control.Measure(LayoutParams.MatchParent,LayoutParams.WrapContent);
            height = Control.MeasuredHeight;
            height = xHeight / aHaight * height;
            if (Element.HeightRequest != height)
                Element.HeightRequest = height;
        }
    }
Atom
źródło