Wyjaśnienie widoku niestandardowego onMeasure


316

Próbowałem zrobić niestandardowy komponent. Rozszerzyłem Viewklasę i wykonałem rysunek onDrawmetodą przesłoniętą. Dlaczego muszę to zmienić onMeasure? Jeśli nie, wszystko wydaje się właściwe. Czy ktoś może to wyjaśnić? Jak mam napisać moją onMeasuremetodę? Widziałem kilka samouczków, ale każdy z nich jest trochę inny niż drugi. Czasami dzwonią super.onMeasurena końcu, czasem używają setMeasuredDimensioni nie dzwonią. Gdzie jest różnica?

W końcu chcę użyć kilku dokładnie takich samych komponentów. Dodałem te składniki do mojego XMLpliku, ale nie wiem, jak duże powinny być. Chcę później ustawić jego pozycję i rozmiar (dlaczego muszę ustawić rozmiar, onMeasurejeśli w onDrawtrakcie rysowania również działa) w niestandardowej klasie komponentów. Kiedy dokładnie muszę to zrobić?

Odpowiedzi:


735

onMeasure()to okazja, by powiedzieć Androidowi, jak duży ma być twój niestandardowy widok od ograniczeń układu dostarczonych przez rodzica; jest to również okazja, aby w widoku niestandardowym dowiedzieć się, jakie są ograniczenia układu (w przypadku, gdy chcesz zachowywać się inaczej w match_parentsytuacji niż w wrap_contentsytuacji). Ograniczenia te są pakowane w MeasureSpecwartości przekazywane do metody. Oto przybliżona korelacja wartości trybu:

  • DOKŁADNIE oznacza, że wartość layout_widthlub layout_heightzostała ustawiona na określoną wartość. Prawdopodobnie powinieneś zrobić widok tego rozmiaru. Można to również uruchomić, gdy match_parentjest używane, aby ustawić rozmiar dokładnie w widoku nadrzędnym (jest to zależne od układu w ramach).
  • AT_MOST zazwyczaj oznacza, że wartość layout_widthlub layout_heightzostała ustawiona na match_parentlub wrap_contenttam, gdzie potrzebny jest maksymalny rozmiar (jest to zależne od układu w strukturze), a rozmiar wymiaru nadrzędnego jest wartością. Nie powinieneś być większy niż ten rozmiar.
  • NIEPECYFIKOWANE zazwyczaj oznacza, że wartość layout_widthlub layout_heightzostała ustawiona wrap_contentbez żadnych ograniczeń. Możesz mieć dowolną wielkość. Niektóre układy również używają tego wywołania zwrotnego, aby ustalić pożądany rozmiar przed określeniem, jakie specyfikacje faktycznie przekazać ponownie w żądaniu drugiego pomiaru.

Umowa, która istnieje onMeasure(), setMeasuredDimension() MUSI być wywoływana na końcu z rozmiarem, jaki chcesz mieć widok. Ta metoda jest wywoływana przez wszystkie implementacje frameworka, w tym implementację domyślną znalezioną w View, dlatego można bezpiecznie wywoływać superzamiast tego, jeśli pasuje to do twojego przypadku użycia.

To prawda, że ​​ponieważ struktura ma domyślną implementację, może nie być konieczne zastąpienie tej metody, ale możesz zobaczyć obcinanie w przypadkach, gdy przestrzeń widoku jest mniejsza niż treść, jeśli nie, i jeśli rozłożysz widok niestandardowy wrap_contentw obu kierunkach może wcale nie być wyświetlany, ponieważ struktura nie wie, jak duży jest!

Ogólnie rzecz biorąc, jeśli przesłonisz, Viewa nie inny istniejący widget, prawdopodobnie dobrym pomysłem jest zapewnienie implementacji, nawet jeśli jest tak prosta jak coś takiego:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int desiredWidth = 100;
    int desiredHeight = 100;

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width;
    int height;

    //Measure Width
    if (widthMode == MeasureSpec.EXACTLY) {
        //Must be this size
        width = widthSize;
    } else if (widthMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        width = Math.min(desiredWidth, widthSize);
    } else {
        //Be whatever you want
        width = desiredWidth;
    }

    //Measure Height
    if (heightMode == MeasureSpec.EXACTLY) {
        //Must be this size
        height = heightSize;
    } else if (heightMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        height = Math.min(desiredHeight, heightSize);
    } else {
        //Be whatever you want
        height = desiredHeight;
    }

    //MUST CALL THIS
    setMeasuredDimension(width, height);
}

Mam nadzieję, że to pomaga.


1
Hej @Devunwired ładne wyjaśnienie najlepsze, jakie czytałem do tej pory. Na twoje wyjaśnienie udzielono odpowiedzi na wiele pytań, które miałem i rozwiałem pewne wątpliwości, ale pozostaje jedno z nich: jeśli mój niestandardowy widok znajduje się w grupie ViewGroup wraz z innymi widokami (nieważne jakie typy), ta grupa ViewGroup otrzyma wszystkie swoje dzieci dla każdej sondy dotyczącej ich ograniczenia LayoutParams i poprosić każde dziecko, aby zmierzyło je zgodnie ze swoimi ograniczeniami?
faraon

47
Pamiętaj, że ten kod nie zadziała, jeśli zastąpisz onMeasure dowolnej podklasy ViewGroup. Twoje widoki podrzędne nie pojawią się i wszystkie będą miały rozmiar 0x0. Jeśli chcesz zastąpić onMeasure niestandardowej grupy ViewGroup, zmień widthMode, widthSize, heightMode i heightSize, skompiluj je z powrotem do MeasureSpecs za pomocą MeasureSpec.makeMeasureSpec i przekaż wynikowe liczby całkowite do super.onMeasure.
Alexey,

1
Fantastyczna odpowiedź. Pamiętaj, że zgodnie z dokumentacją Google odpowiedzialność za wypełnianie ponosi View.
jonstaff

4
Zbyt skomplikowane c ** p, które sprawia, że ​​Android jest bolesnym systemem do pracy. Mogliby mieć właśnie getParent (). Get *** () ...
Oliver Dixon

2
W Viewklasie są metody pomocnicze , nazywane resolveSizeAndStatei resolveSize, które powinny robić to, co robią klauzule „if” - uznałem je za przydatne, szczególnie jeśli trzeba często pisać te IF.
stan0

5

twoja odpowiedź nie jest kompletna, ponieważ wartości zależą również od opakowania. W przypadku układów względnych lub liniowych wartości zachowują się w następujący sposób:

  • DOKŁADNIE match_parent to DOKŁADNIE + rozmiar rodzica
  • AT_MOST wrap_content powoduje AT_MOST MeasureSpec
  • UNSPECIFIED nigdy nie zadziałał

W przypadku przewijania w poziomie kod będzie działał.


57
Jeśli uważasz, że niektóre odpowiedzi tutaj są niekompletne, dodaj je zamiast dać odpowiedź częściową.
Michaël

1
Dobry onya za powiązanie tego z działaniem układów, ale w moim przypadku onMeasure jest wywoływany trzykrotnie w moim widoku niestandardowym. Widok, o którym mowa, miał wysokość wrap_content i ważoną szerokość (szerokość = 0, waga = 1). Pierwsze połączenie miało wartość UNSPECIFIED / UNSPECIFIED, drugie miało AT_MOST / EXACTLY, a trzecie miało EXACTLY / EXACTLY.
William T. Mallard

0

Jeśli nie musisz nic zmieniać w Pomiarze - absolutnie nie musisz tego zastępować.

Kod Devunwired (tutaj wybrana i najczęściej głosowana odpowiedź) jest prawie identyczny z tym, co już dla ciebie zrobiła implementacja SDK (i sprawdziłem - zrobiło to od 2009 roku).

Możesz sprawdzić metodę onMeasure tutaj :

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

Przesłanianie kodu SDK, który ma zostać zastąpiony dokładnie tym samym kodem, nie ma sensu.

Ten oficjalny dokument, który twierdzi, że „domyślna właściwość onMeasure () zawsze ustawia rozmiar 100 x 100” - jest błędna.

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.