Erklärung der benutzerdefinierten Ansicht von onMeasure

316

Ich habe versucht, eine benutzerdefinierte Komponente zu erstellen. Ich habe die ViewKlasse erweitert und in onDrawüberschriebener Methode gezeichnet . Warum muss ich überschreiben onMeasure? Wenn nicht, wäre alles richtig. Darf es jemand erklären? Wie soll ich meine onMeasureMethode schreiben ? Ich habe einige Tutorials gesehen, aber jedes ist ein bisschen anders als das andere. Manchmal rufen sie super.onMeasuream Ende an, manchmal benutzen sie setMeasuredDimensionund haben es nicht angerufen. Wo ist ein Unterschied?

Immerhin möchte ich mehrere genau die gleichen Komponenten verwenden. Ich habe diese Komponenten zu meiner XMLDatei hinzugefügt , weiß aber nicht, wie groß sie sein sollen. Ich möchte später seine Position und Größe einstellen (warum ich festgelegte Größe müssen , onMeasurewenn in , onDrawwenn ich es zeichnen, ist so gut funktioniert) in benutzerdefinierten Komponentenklasse. Wann genau muss ich das tun?

Sennin
quelle

Antworten:

734

onMeasure()ist Ihre Gelegenheit, Android mitzuteilen, wie groß Ihre benutzerdefinierte Ansicht von den vom Elternteil bereitgestellten Layoutbeschränkungen abhängen soll. Es ist auch die Gelegenheit Ihrer benutzerdefinierten Ansicht, die Layoutbeschränkungen zu erfahren (falls Sie sich in einer match_parentSituation anders als in einer Situation verhalten möchten wrap_content). Diese Einschränkungen werden in die MeasureSpecWerte gepackt, die an die Methode übergeben werden. Hier ist eine grobe Korrelation der Moduswerte:

  • GENAU bedeutet, dass der Wert layout_widthoder layout_heightauf einen bestimmten Wert gesetzt wurde. Sie sollten Ihre Ansicht wahrscheinlich auf diese Größe einstellen. Dies kann auch ausgelöst werden, wenn match_parentes verwendet wird, um die Größe genau auf die übergeordnete Ansicht einzustellen (dies ist im Framework vom Layout abhängig).
  • AT_MOST bedeutet normalerweise, dass der Wert layout_widthoder layout_heightauf festgelegt wurde match_parentoder wrap_contentwo eine maximale Größe benötigt wird (dies ist vom Framework abhängig), und die Größe der übergeordneten Dimension ist der Wert. Sie sollten nicht größer als diese Größe sein.
  • UNSPECIFIED bedeutet normalerweise, dass der Wert layout_widthoder ohne Einschränkungen festgelegt wurde. Sie können jede Größe haben, die Sie möchten. Einige Layouts verwenden diesen Rückruf auch, um Ihre gewünschte Größe zu ermitteln, bevor Sie festlegen, welche Spezifikationen Sie in einer zweiten Kennzahlanforderung tatsächlich erneut übergeben sollen.layout_heightwrap_content

Der Vertrag, der mit onMeasure()besteht, setMeasuredDimension() muss am Ende mit der Größe aufgerufen werden, die die Ansicht haben soll. Diese Methode wird von allen Framework-Implementierungen aufgerufen, einschließlich der Standardimplementierung in View. Daher ist es sicher, superstattdessen aufzurufen, wenn dies Ihrem Anwendungsfall entspricht.

Zugegeben, da das Framework eine Standardimplementierung anwendet, müssen Sie diese Methode möglicherweise nicht überschreiben. In Fällen, in denen der Ansichtsbereich kleiner als Ihr Inhalt ist, wird möglicherweise ein Clipping angezeigt, wenn Sie dies nicht tun und wenn Sie Ihre festlegen Benutzerdefinierte Ansicht mit wrap_contentin beide Richtungen, Ihre Ansicht wird möglicherweise überhaupt nicht angezeigt, da das Framework nicht weiß, wie groß es ist!

Wenn Sie überschreiben Viewund kein anderes vorhandenes Widget verwenden, ist es im Allgemeinen wahrscheinlich eine gute Idee, eine Implementierung bereitzustellen, auch wenn dies so einfach ist:

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

Ich hoffe, das hilft.

Devunwired
quelle
1
Hey @ Devunwired nette Erklärung das Beste, was ich bisher gelesen habe. Ihre Erklärung hat viele Fragen beantwortet, die ich hatte, und einige Zweifel ausgeräumt, aber es bleibt noch eine: Wenn sich meine benutzerdefinierte Ansicht in einer ViewGroup befindet, zusammen mit einigen anderen Ansichten (egal welche Typen), erhält ViewGroup alle seine Kinder für jede einzelne Probe für ihre LayoutParams-Einschränkung und bitten Sie jedes Kind, es selbst entsprechend seinen Einschränkungen zu messen?
Pharao
47
Beachten Sie, dass dieser Code nicht funktioniert, wenn Sie onMeasure einer ViewGroup-Unterklasse überschreiben. Ihre Unteransichten werden nicht angezeigt und haben alle eine Größe von 0x0. Wenn Sie onMeasure einer benutzerdefinierten ViewGroup überschreiben müssen, ändern Sie widthMode, widthSize, heightMode und heightSize, kompilieren Sie sie mit MeasureSpec.makeMeasureSpec zurück zu MeasureSpecs und übergeben Sie die resultierenden Ganzzahlen an super.onMeasure.
Alexey
1
Fantastische Antwort. Beachten Sie, dass gemäß der Dokumentation von Google die Verantwortung für das Auffüllen in der Ansicht liegt.
Jonstaff
4
Über kompliziertes c ** p, das Android zu einem schmerzhaften Layoutsystem macht. Sie hätten gerade getParent () haben können. Get *** () ...
Oliver Dixon
2
Es gibt Hilfsmethoden in der ViewKlasse, aufgerufen resolveSizeAndStateund resolveSize, die das tun sollten, was die 'if'-Klauseln tun - ich fand sie nützlich, insbesondere wenn Sie diese IFs häufig schreiben müssen.
stan0
5

Tatsächlich ist Ihre Antwort nicht vollständig, da die Werte auch vom Verpackungscontainer abhängen. Bei relativen oder linearen Layouts verhalten sich die Werte wie folgt:

  • GENAU match_parent ist GENAU + Größe des übergeordneten Elements
  • AT_MOST wrap_content führt zu einer AT_MOST MeasureSpec
  • UNSPECIFIED wurde nie ausgelöst

Bei einer horizontalen Bildlaufansicht funktioniert Ihr Code.

Florian
quelle
57
Wenn Sie der Meinung sind, dass eine Antwort hier unvollständig ist, fügen Sie sie bitte hinzu, anstatt eine teilweise Antwort zu geben.
Michaël
1
Gute Onya, um dies mit der Funktionsweise von Layouts zu verknüpfen, aber in meinem Fall wird onMeasure für meine benutzerdefinierte Ansicht dreimal aufgerufen. Die fragliche Ansicht hatte eine wrap_content-Höhe und eine gewichtete Breite (width = 0, weight = 1). Der erste Anruf hatte UNSPECIFIED / UNSPECIFIED, der zweite hatte AT_MOST / EXACTLY und der dritte hatte EXACTLY / EXACTLY.
William T. Mallard
0

Wenn Sie onMeasure nicht ändern müssen, müssen Sie es absolut nicht überschreiben.

Devunwired-Code (die ausgewählte und am häufigsten gewählte Antwort hier) ist fast identisch mit dem, was die SDK-Implementierung bereits für Sie erledigt (und ich habe überprüft, dass dies seit 2009 geschehen ist).

Sie können die onMeasure-Methode hier überprüfen :

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

Das Überschreiben des SDK-Codes, der durch genau denselben Code ersetzt werden soll, macht keinen Sinn.

Das Stück dieses offiziellen Dokuments , in dem behauptet wird, dass "die Standardeinstellung für onMeasure () immer eine Größe von 100 x 100 festlegt", ist falsch.

David Refaeli
quelle