Autorytatywny sposób na przesłonięcie onMeasure ()?

88

Jaki jest prawidłowy sposób przesłaniania onMeasure ()? Widziałem różne podejścia. Na przykład Professional Android Development używa MeasureSpec do obliczenia wymiarów, a następnie kończy się wywołaniem metody setMeasuredDimension (). Na przykład:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
}

Z drugiej strony, zgodnie z tym postem , „poprawnym” sposobem jest użycie MeasureSpec, wywołanie setMeasuredDimensions (), a następnie wywołanie setLayoutParams () i zakończenie wywołaniem super.onMeasure (). Na przykład:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
this.setLayoutParams(new *ParentLayoutType*.LayoutParams(parentWidth/2,parentHeight));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Więc jaka jest właściwa droga? Żadne z tych podejść nie zadziałało dla mnie w 100%.

Myślę, że naprawdę pytam, czy ktoś zna samouczek, który wyjaśnia onMeasure (), układ, wymiary widoków potomnych itp.?

U Avalos
źródło
15
czy uważasz, że odpowiedź była przydatna / poprawna? dlaczego nie oznaczyć ją jako na odpowiedź?
superjos

Odpowiedzi:

68

Pozostałe rozwiązania nie są kompleksowe. W niektórych przypadkach mogą działać i są dobrym miejscem do rozpoczęcia, ale nie ma gwarancji, że będą działać.

Kiedy zadzwoni onMeasure, możesz mieć prawo do zmiany rozmiaru lub nie. Wartości, które są przekazywane do twojego onMeasure ( widthMeasureSpec, heightMeasureSpec) zawierają informacje o tym, co twój widok podrzędny może robić. Obecnie istnieją trzy wartości:

  1. MeasureSpec.UNSPECIFIED - Możesz być tak duży, jak chcesz
  2. MeasureSpec.AT_MOST- Tak duży, jak chcesz (do rozmiaru specyfikacji), to jest parentWidthw twoim przykładzie.
  3. MeasureSpec.EXACTLY- Bez wyboru. Rodzic wybrał.

Dzieje się tak, aby system Android mógł wykonać wiele przejść, aby znaleźć odpowiedni rozmiar dla każdego elementu. Więcej informacji można znaleźć tutaj .

Jeśli nie będziesz przestrzegać tych zasad, Twoje podejście nie będzie gwarantowane.

Na przykład, jeśli chcesz sprawdzić, czy w ogóle możesz zmienić rozmiar, możesz wykonać następujące czynności:

final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;

Korzystając z tych informacji, będziesz wiedział, czy możesz modyfikować wartości w swoim kodzie. Lub jeśli musisz zrobić coś innego. Szybkim i łatwym sposobem ustalenia żądanego rozmiaru jest użycie jednej z następujących metod:

int solutionSizeAndState (int size, int measureSpec, int childMeasuredState)

intolveSize (int size, int measureSpec)

Podczas gdy pierwsza jest dostępna tylko w Honeycomb, druga jest dostępna we wszystkich wersjach.

Uwaga: może się okazać, że to resizeWidthlub resizeHeightzawsze jest fałszywe. Okazało się, że tak jest, gdy o to prosiłem MATCH_PARENT. Udało mi się to naprawić, żądając WRAP_CONTENTw moim układzie nadrzędnym, a następnie podczas fazy NIEOKREŚLONEJ, żądając rozmiaru Integer.MAX_VALUE. Dzięki temu uzyskasz maksymalny rozmiar, na jaki zezwala twój rodzic podczas następnego przejścia przez onMeasure.

Grimmace
źródło
39

Dokumentacja jest autorytetem w tej sprawie: http://developer.android.com/guide/topics/ui/how-android-draws.html i http://developer.android.com/guide/topics/ui/custom -components.html

Podsumowując: na końcu swojej nadpisanej onMeasuremetody powinieneś wywołać setMeasuredDimension.

Nie powinieneś dzwonić super.onMeasurepo wywołaniu setMeasuredDimension, to po prostu wykasuje wszystko, co ustawisz. W niektórych sytuacjach możesz chcieć wywołać super.onMeasurepierwszy, a następnie zmodyfikować wyniki, wywołując setMeasuredDimension.

Nie nazywaj setLayoutParamssię onMeasure. Układ następuje w drugim przejściu po pomiarze.

satur9nine
źródło
Z doświadczenia wiem, że jeśli nadpisuję onMeasure bez wywoływania super.onMeasure, OnLayout nie jest wywoływany w widokach potomnych.
William Jockusch,
2

Myślę, że to zależy od rodzica, nad którym masz pierwszeństwo.

Na przykład, jeśli rozszerzasz ViewGroup (jak FrameLayout), po zmierzeniu rozmiaru powinieneś wywołać jak poniżej

super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));

ponieważ możesz chcieć ViewGroup zrobić resztę pracy (zrobić kilka rzeczy w widoku dziecka)

Jeśli rozszerzasz View (jak ImageView), możesz po prostu zadzwonić this.setMeasuredDimension(width, height);, ponieważ klasa nadrzędna po prostu zrobi coś, co zwykle robiłeś.

super.onMeasure()Jednym słowem, jeśli chcesz, aby niektóre funkcje oferowane przez twoją klasę nadrzędną były darmowe, powinieneś zadzwonić (zwykle w trybie MeasureSpec.EXACTLY), w przeciwnym razie this.setMeasuredDimension(width, height);wystarczy wywołanie .

TAM
źródło
1

oto jak rozwiązałem problem:

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

        ....

        setMeasuredDimension( measuredWidth, measuredHeight );

        widthMeasureSpec = MeasureSpec.makeMeasureSpec( measuredWidth, MeasureSpec.EXACTLY );
        heightMeasureSpec = MeasureSpec.makeMeasureSpec( measuredHeight, MeasureSpec.EXACTLY);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

Ponadto było to konieczne dla komponentu ViewPager

Yuli Reiri
źródło
3
To nie jest właściwe rozwiązanie. super.onMeasureczyści wymiary ustawione za pomocą setMeasuredDimension.
tomrozb
@tomrozb to nie jest humptydevelopers.com/2013/05/ ... i możesz sprawdzić źródła Androida
Yuli Reiri Kwietnia
@YuliReiri: Idealne rozwiązanie!
Shayan Tabatabaee
0

W przypadku zmiany rozmiaru widoków w środku onMeasurewszystko czego potrzebujesz to setMeasuredDimensionzadzwoń. Jeśli zmieniasz rozmiar poza onMeasuresobą, musisz zadzwonić setLayoutParams. Na przykład zmiana rozmiaru widoku tekstu po zmianie tekstu.

Kyle O.
źródło
Nadal można wywoływać metody setMinWidth () i setMaxWidth () i obsługiwać to standardowe narzędzie onMeasure. Zauważyłem, że lepiej nie wywoływać metody setLayoutParams z wnętrza niestandardowej klasy View. Jest naprawdę używany w przypadku grup widoków lub działań do zastępowania zachowania widoku podrzędnego.
Dorrin
0

Zależy od używanego sterowania. Instrukcje zawarte w dokumentacji działają dla niektórych kontrolek (TextView, Button, ...), ale nie dla innych (LinearLayout, ...). Sposobem, który działał dla mnie bardzo dobrze, było zadzwonienie do super, kiedy skończę. Na podstawie artykułu w poniższym linku.

http://humptydevelopers.blogspot.in/2013/05/android-view-overriding-onmeasure.html

Aviral
źródło
-1

Wydaje mi się, że setLayoutParams i ponowne obliczenie pomiarów jest obejściem umożliwiającym poprawną zmianę rozmiaru widoków podrzędnych, tak jak zwykle jest to wykonywane w onMeasure klasy pochodnej.

Jednak rzadko to działa poprawnie (z jakiegokolwiek powodu ...), lepiej wywołaj MeasureChildren (podczas tworzenia ViewGroup) lub spróbuj czegoś podobnego, gdy jest to konieczne.

M. Schenk
źródło
-5

możesz wziąć ten fragment kodu jako przykład onMeasure () ::

public class MyLayerLayout extends RelativeLayout {

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);

        int currentChildCount = getChildCount();
        for (int i = 0; i < currentChildCount; i++) {
            View currentChild = getChildAt(i);

            //code to find information

            int widthPercent = currentChildInfo.getWidth();
            int heightPercent = currentChildInfo.getHeight();

//considering we will pass height & width as percentage

            int myWidth = (int) Math.round(parentWidth * (widthPercent / 100.0));
            int myHeight = (int) Math.round(parentHeight * (heightPercent / 100.0));

//Considering we need to set horizontal & vertical position of the view in parent

            AlignmentTraitValue vAlign = currentChildInfo.getVerticalLocation() != null ? currentChildlayerInfo.getVerticalLocation() : currentChildAlignmentTraitValue.TOP;
            AlignmentTraitValue hAlign = currentChildInfo.getHorizontalLocation() != null ? currentChildlayerInfo.getHorizontalLocation() : currentChildAlignmentTraitValue.LEFT;
            int topPadding = 0;
            int leftPadding = 0;

            if (vAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                topPadding = (parentHeight - myHeight) / 2;
            } else if (vAlign.equals(currentChildAlignmentTraitValue.BOTTOM)) {
                topPadding = parentHeight - myHeight;
            }

            if (hAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                leftPadding = (parentWidth - myWidth) / 2;
            } else if (hAlign.equals(currentChildAlignmentTraitValue.RIGHT)) {
                leftPadding = parentWidth - myWidth;
            }
            LayoutParams myLayoutParams = new LayoutParams(myWidth, myHeight);
            currentChildLayoutParams.setMargins(leftPadding, topPadding, 0, 0);
            currentChild.setLayoutParams(myLayoutParams);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
Sayan
źródło
6
Ten kod jest zasadniczo błędny. Po pierwsze, nie bierze pod uwagę trybu układu (patrz odpowiedź Grimmace). To źle, ale nie zobaczysz tego efektu, ponieważ wywołujesz również super.onMeasure () na samym końcu, co zastępuje ustawione wartości (patrz odpowiedź satur9nine) i w ogóle niweczy cel nadpisania onMeasure ().
spaaarky21