Autorisierender Weg, um onMeasure () zu überschreiben?

87

Wie kann onMeasure () richtig überschrieben werden? Ich habe verschiedene Ansätze gesehen. Beispielsweise verwendet die professionelle Android-Entwicklung MeasureSpec, um die Abmessungen zu berechnen, und endet dann mit einem Aufruf von setMeasuredDimension (). Beispielsweise:

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

Auf der anderen Seite besteht gemäß diesem Beitrag die "richtige" Methode darin, MeasureSpec zu verwenden, setMeasuredDimensions () aufzurufen, gefolgt von einem Aufruf von setLayoutParams (), und mit einem Aufruf von super.onMeasure () zu enden. Beispielsweise:

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

Welches ist der richtige Weg? Keiner der beiden Ansätze hat bei mir zu 100% funktioniert.

Ich denke wirklich, was ich frage, ist, dass jemand ein Tutorial kennt, das onMeasure (), Layout, Abmessungen von untergeordneten Ansichten usw. erklärt?

U Avalos
quelle
15
Fanden Sie die Antwort nützlich / richtig? warum markieren Sie es nicht wie die Antwort?
Superjos

Antworten:

68

Die anderen Lösungen sind nicht umfassend. In einigen Fällen können sie funktionieren und sind ein guter Ausgangspunkt, aber es kann nicht garantiert werden, dass sie funktionieren.

Wenn onMeasure aufgerufen wird, haben Sie möglicherweise das Recht, die Größe zu ändern. Die Werte, die an onMeasure ( widthMeasureSpec, heightMeasureSpec) übergeben werden, enthalten Informationen darüber, was Ihre untergeordnete Ansicht tun darf. Derzeit gibt es drei Werte:

  1. MeasureSpec.UNSPECIFIED - Sie können so groß sein, wie Sie möchten
  2. MeasureSpec.AT_MOST- So groß wie Sie möchten (bis zur Spezifikationsgröße). Dies ist parentWidthin Ihrem Beispiel.
  3. MeasureSpec.EXACTLY- Keine Wahl. Elternteil hat gewählt.

Dies geschieht so , dass Android mehrere Durchgänge machen , die richtige Größe für jeden Artikel zu finden, siehe hier für weitere Details.

Wenn Sie diese Regeln nicht befolgen, kann nicht garantiert werden, dass Ihr Ansatz funktioniert.

Wenn Sie beispielsweise überprüfen möchten, ob Sie die Größe überhaupt ändern dürfen, können Sie Folgendes tun:

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

Anhand dieser Informationen wissen Sie, ob Sie die Werte wie in Ihrem Code ändern können. Oder wenn Sie etwas anderes tun müssen. Eine schnelle und einfache Möglichkeit, die gewünschte Größe aufzulösen, ist die Verwendung einer der folgenden Methoden:

int resolveSizeAndState (int size, int MeasureSpec, int childMeasuredState)

int resolveSize (int size, int MeasureSpec)

Während die erste nur für Honeycomb verfügbar ist, ist die zweite für alle Versionen verfügbar.

Hinweis: Sie können feststellen , dass resizeWidthoder resizeHeightsind immer falsch. Ich fand dies der Fall, wenn ich darum bat MATCH_PARENT. Ich konnte dies beheben, indem WRAP_CONTENTich in meinem übergeordneten Layout und dann während der UNSPECIFIED-Phase eine Größe von anforderte Integer.MAX_VALUE. Auf diese Weise erhalten Sie die maximale Größe, die Ihre Eltern beim nächsten Durchgang durch onMeasure zulassen.

Grimmace
quelle
39

Die Dokumentation ist die Autorität in dieser Angelegenheit: http://developer.android.com/guide/topics/ui/how-android-draws.html und http://developer.android.com/guide/topics/ui/custom -components.html

Zusammenfassend: Am Ende Ihrer überschriebenen onMeasureMethode sollten Sie aufrufen setMeasuredDimension.

Sie sollten nicht super.onMeasurenach dem Anruf anrufen setMeasuredDimension, da dies nur das löscht, was Sie eingestellt haben. In einigen Situationen möchten Sie möglicherweise den super.onMeasureersten aufrufen und dann die Ergebnisse durch Aufrufen ändern setMeasuredDimension.

Rufen Sie nicht setLayoutParamsan onMeasure. Das Layout erfolgt in einem zweiten Durchgang nach dem Messen.

gesättigt neun
quelle
Ich habe die Erfahrung gemacht, dass OnLayout nicht für untergeordnete Ansichten aufgerufen wird, wenn ich onMeasure überschreibe, ohne super.onMeasure aufzurufen.
William Jockusch
2

Ich denke, es hängt vom Elternteil ab, was Sie überschreiben.

Wenn Sie beispielsweise eine ViewGroup (wie FrameLayout) erweitern und die Größe gemessen haben, sollten Sie wie folgt aufrufen

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

weil Sie ViewGroup möglicherweise für die Restarbeit verwenden möchten (einige Aufgaben in der untergeordneten Ansicht ausführen)

Wenn Sie eine Ansicht erweitern (wie ImageView), können Sie einfach aufrufen this.setMeasuredDimension(width, height);, da die übergeordnete Klasse genau das tut, was Sie normalerweise getan haben.

super.onMeasure()Kurz gesagt , wenn Sie einige Funktionen wünschen, die Ihre Elternklasse kostenlos anbietet, sollten Sie aufrufen (normalerweise MeasureSpec.EXACTLY-Modus messen), andernfalls ist ein Aufruf this.setMeasuredDimension(width, height);ausreichend.

Yon
quelle
1

So habe ich das Problem gelöst:

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

}}

Darüber hinaus war es für die ViewPager-Komponente erforderlich

Yuli Reiri
quelle
3
Dies ist keine richtige Lösung. super.onMeasurelöscht die mit setMeasuredDimension.
Tomrozb
@ Tomrozb es ist nicht humptydevelopers.com/2013/05/… und Sie können Android-Quellen überprüfen
Yuli Reiri
@ YuliReiri: Perfekte Lösung!
Shayan Tabatabaee
0

Wenn Sie die Ansichtsgröße innerhalb von onMeasureallem ändern, was Sie brauchen, ist der setMeasuredDimensionAnruf. Wenn Sie die Größe außerhalb von ändern onMeasure, müssen Sie anrufen setLayoutParams. Ändern Sie beispielsweise die Größe einer Textansicht, wenn der Text geändert wird.

Kyle O.
quelle
Sie können weiterhin setMinWidth () und setMaxWidth () aufrufen und das Standard-onMeasure damit umgehen lassen. Ich habe festgestellt, dass es besser ist, setLayoutParams nicht innerhalb einer benutzerdefinierten View-Klasse aufzurufen. Es wird wirklich für ViewGroups oder Aktivitäten verwendet, um das Verhalten der untergeordneten Ansicht zu überschreiben.
Dorrin
0

Hängt von der Steuerung ab, die Sie verwenden. Die Anweisungen in der Dokumentation gelten für einige Steuerelemente (TextView, Button, ...), nicht jedoch für andere (LinearLayout, ...). Die Art und Weise, wie es für mich sehr gut funktioniert hat, war, den Super anzurufen, sobald ich fertig bin. Basierend auf dem Artikel im folgenden Link.

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

Aviral
quelle
-1

Ich denke, setLayoutParams und die Neuberechnung der Messungen sind eine Problemumgehung, um die Größe der untergeordneten Ansichten korrekt zu ändern, wie dies normalerweise in onMeasure der abgeleiteten Klasse erfolgt.

Dies funktioniert jedoch selten richtig (aus welchem ​​Grund auch immer ...), rufen Sie besser MeasureChildren auf (wenn Sie eine ViewGroup ableiten) oder versuchen Sie bei Bedarf etwas Ähnliches.

M. Schenk
quelle
-5

Sie können diesen Code als Beispiel für onMeasure () verwenden ::

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
quelle
6
Dieser Code ist grundsätzlich falsch. Erstens wird der Layoutmodus nicht berücksichtigt (siehe Grimmaces Antwort). Das ist schlecht, aber Sie werden den Effekt nicht sehen, weil Sie ganz am Ende auch super.onMeasure () aufrufen, wodurch die von Ihnen festgelegten Werte überschrieben werden (siehe Antwort von Satur9nine) und der Zweck, onMeasure () überhaupt zu überschreiben, zunichte gemacht wird.
spaaarky21