Bestimmen der Größe einer Android-Ansicht zur Laufzeit

123

Ich versuche, eine Animation auf eine Ansicht in meiner Android-App anzuwenden, nachdem meine Aktivität erstellt wurde. Dazu muss ich die aktuelle Größe der Ansicht bestimmen und dann eine Animation einrichten, die von der aktuellen Größe auf die neue Größe skaliert. Dieser Teil muss zur Laufzeit ausgeführt werden, da die Ansicht je nach Benutzereingabe auf unterschiedliche Größen skaliert werden kann. Mein Layout ist in XML definiert.

Dies scheint eine leichte Aufgabe zu sein, und es gibt viele SO-Fragen dazu, obwohl keine offensichtlich mein Problem gelöst hat. Vielleicht fehlt mir etwas Offensichtliches. Ich bekomme einen Überblick über meine Ansicht durch:

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

Dies funktioniert gut, aber wenn Sie anrufen getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().widthusw., sie alle zurück 0. Ich habe auch manuell aufrufen versucht , measure()auf der Ansicht , gefolgt von einem Aufruf getMeasuredWidth(), aber das hat keine Wirkung.

Ich habe versucht, diese Methoden aufzurufen und das Objekt im Debugger in meiner Aktivität onCreate()und in zu untersuchen onPostCreate(). Wie kann ich zur Laufzeit die genauen Abmessungen dieser Ansicht ermitteln?

Nik Reiman
quelle
1
Oh, ich sollte auch beachten , dass die Sicht sich definitiv nicht nicht 0 haben Breite / Höhe. Es erscheint gut auf dem Bildschirm.
Nik Reiman
Können Sie das <ImageView ... /> -Tag aus dem XML-Layout posten?
Fhucho
Ich hatte mit vielen SO-Lösungen für dieses Problem zu kämpfen, bis mir klar wurde, dass in meinem Fall die Abmessungen der Ansicht mit dem physischen Bildschirm übereinstimmten (meine App ist "immersiv" und die Ansicht und alle Breiten und Höhen ihrer Eltern sind auf eingestellt match_parent). In diesem Fall besteht eine einfachere Lösung, die sicher aufgerufen werden kann, noch bevor die Ansicht gezeichnet wird (z. B. aus der onCreate () -Methode Ihrer Aktivität), darin, nur Display.getSize () zu verwenden. Weitere Informationen finden Sie unter stackoverflow.com/a/30929599/5025060 . Ich weiß, dass @NikReiman nicht darum gebeten hat, sondern nur eine Notiz für diejenigen hinterlassen hat, die diese einfachere Methode möglicherweise anwenden können.
CODE-REaD

Antworten:

180

Verwenden Sie den ViewTreeObserver in der Ansicht, um auf das erste Layout zu warten. Erst nach dem ersten Layout funktioniert getWidth () / getHeight () / getMeasuredWidth () / getMeasuredHeight ().

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
quelle
21
Seien Sie vorsichtig mit dieser Antwort, da sie fortlaufend aufgerufen wird, wenn die zugrunde liegende Ansicht ein Video / eine Oberfläche ist
Kevin Parker
7
Können Sie das @ Kevin näher erläutern? Zwei Dinge, erstens, da es sich um einen Listener handelt, würde ich einen einzelnen Rückruf erwarten, und als nächstes entfernen wir den Listener, sobald wir den ersten Rückruf erhalten. Habe ich etwas verpasst?
Vikram Bodicherla
4
Vor dem Aufrufen von ViewTreeObserver-Methoden wird empfohlen, isAlive () zu überprüfen: if (view.getViewTreeObserver (). IsAlive ()) {view.getViewTreeObserver (). RemoveGlobalOnLayoutListener (this); }
Peter Tran
4
irgendeine Alternative zu removeGlobalOnLayoutListener(this);? weil es veraltet ist.
Sibbs Gambling
7
@FarticlePilter, verwenden Sie stattdessen removeOnGlobalLayoutListener (). Es ist in den Dokumenten erwähnt.
Vikram Bodicherla
63

Je nach Szenario gibt es tatsächlich mehrere Lösungen:

  1. Die sichere Methode funktioniert unmittelbar vor dem Zeichnen der Ansicht, nachdem die Layoutphase abgeschlossen ist:
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); 
}

Beispielnutzung:

    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. In einigen Fällen reicht es aus, die Größe der Ansicht manuell zu messen:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth(); 
int height=view.getMeasuredHeight();

Wenn Sie die Größe des Containers kennen:

    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. Wenn Sie eine benutzerdefinierte Ansicht haben, die Sie erweitert haben, können Sie deren Größe mit der Methode "onMeasure" ermitteln. Ich denke jedoch, dass dies nur in einigen Fällen gut funktioniert:
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. Wenn Sie in Kotlin schreiben, können Sie die nächste Funktion verwenden, die hinter den Kulissen genau so funktioniert, wie runJustBeforeBeingDrawnich es geschrieben habe:

    view.doOnPreDraw { actionToBeTriggered() }

    Beachten Sie, dass Sie dies zu gradle hinzufügen müssen (finden Sie hier ):

    implementation 'androidx.core:core-ktx:#.#'
Android-Entwickler
quelle
1
Scheint, dass Lösung Nr. 1 nicht immer funktioniert OnPreDrawListener. Getesteter Fall, wenn Sie Code von Activity's ausführen möchten onCreate()und sofort eine Hintergrund-App haben. Besonders bei langsameren Geräten leicht zu wiederholen. Wenn Sie ersetzen OnPreDrawListenermit OnGlobalLayoutListener- Sie werden nicht gleiches Problem sehen.
Fragesteller
@questioner Ich verstehe nicht, aber ich bin froh, dass es dir am Ende geholfen hat.
Android-Entwickler
Obwohl ich normalerweise ViewTreeObserveroder benutze post, hat in meinem Fall Nummer 2 geholfen ( view.measure).
CoolMind
3
@CoolMind Sie können auch eine neue Methode verwenden, falls Sie Kotlin verwenden. Die Antwort wurde aktualisiert, um sie auch einzuschließen.
Android-Entwickler
Warum hat Kotlin dafür zusätzliche Funktionen? Google ist verrückt, diese Sprache ist nutzlos (sollte nur bei Java bleiben), sie ist viel älter als schnell, aber nur schnell hat genug Popularität tiobe.com/tiobe-index (schnelle 12 Position und Kotlin 38)
user924
18

Rufen Sie an, getWidth()bevor die Ansicht tatsächlich auf dem Bildschirm angezeigt wird?

Ein häufiger Fehler neuer Android-Entwickler besteht darin, die Breite und Höhe einer Ansicht in ihrem Konstruktor zu verwenden. Wenn der Konstruktor einer Ansicht aufgerufen wird, weiß Android noch nicht, wie groß die Ansicht sein wird, daher werden die Größen auf Null gesetzt. Die tatsächlichen Größen werden während der Layoutphase berechnet, die nach dem Bau, aber bevor etwas gezeichnet wird, erfolgt. Sie können die onSizeChanged()Methode verwenden, um über die Werte informiert zu werden, wenn diese bekannt sind, oder Sie können die Methoden getWidth()und getHeight()später verwenden, z. B. in der onDraw()Methode.

Mark B.
quelle
2
Bedeutet das also, dass ich ImageView überschreiben muss, um das onSizeChange()Ereignis zu erfassen und die tatsächliche Größe zu ermitteln? Das scheint ein bisschen übertrieben zu sein ... obwohl ich an diesem Punkt verzweifelt bin, nur um den Code zum Laufen zu bringen, also ist es einen Versuch wert.
Nik Reiman
Ich dachte eher daran zu warten, bis Ihr onCreate () in Ihrer Aktivität beendet ist.
Mark B
1
Wie bereits erwähnt, habe ich versucht, diesen Code onPostCreate()einzufügen, der ausgeführt wird, nachdem die Ansicht vollständig erstellt und gestartet wurde.
Nik Reiman
1
Woher kommt der zitierte Text?
Komplikationen
1
Dieses Zitat für die benutzerdefinierte Ansicht, posten Sie es nicht für eine solche Antwort, wenn wir nach der Größe der Ansicht im Allgemeinen fragen
user924
17

Basierend auf @ mbairds Ratschlägen fand ich eine praktikable Lösung, indem ich die ImageViewKlasse unterordnete und überschrieb onLayout(). Anschließend erstellte ich eine Beobachterschnittstelle, die meine Aktivität implementierte, und übergab einen Verweis auf sich selbst an die Klasse, sodass sie der Aktivität mitteilen konnte, wann die Dimensionierung tatsächlich abgeschlossen war.

Ich bin nicht zu 100% davon überzeugt, dass dies die beste Lösung ist (daher markiere ich diese Antwort noch nicht als richtig), aber es funktioniert und laut Dokumentation ist es das erste Mal, dass man die tatsächliche Größe einer Ansicht findet.

Nik Reiman
quelle
Gut zu wissen. Wenn ich etwas finde, mit dem Sie umgehen können, dass Sie die Ansicht unterordnen müssen, werde ich es hier veröffentlichen. Ich bin etwas überrascht, dass onPostCreate () nicht funktioniert hat.
Mark B
Nun, ich habe es versucht und es scheint zu funktionieren. Nur nicht wirklich die beste Lösung, da diese Methode häufig nicht nur einmal beim Start aufgerufen wird. :(
Tobias Reich
10

Hier ist der Code zum Abrufen des Layouts durch Überschreiben einer Ansicht, wenn API <11 (API 11 enthält die View.OnLayoutChangedListener-Funktion):

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
quelle
6

Sie können diese Frage überprüfen . Sie können die post () -Methode von View's verwenden.

Macarse
quelle
5

Verwenden Sie den folgenden Code, um die Größe der Ansicht anzugeben.

@Override
public void onWindowFocusChanged(boolean hasFocus) {
       super.onWindowFocusChanged(hasFocus);
       Log.e("WIDTH",""+view.getWidth());
       Log.e("HEIGHT",""+view.getHeight());
}
Asif Patel
quelle
5

Das funktioniert bei mir in meinem 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
quelle
4
Sie müssen nicht verzögert veröffentlichen, wenn es sich in einem Klick-Listener befindet. Sie können nichts anklicken, bis alle Ansichten selbst gemessen und auf dem Bildschirm angezeigt wurden. Somit werden sie bereits gemessen, wenn Sie darauf klicken können.
Afollestad
Dieser Code ist falsch. Die Verzögerung nach dem Eingriff garantiert nicht, dass Ihre Ansicht beim nächsten Tick gemessen wird.
Ian Wong
Ich würde empfehlen, nicht auf postDelayed () zu setzen. Es ist unwahrscheinlich, dass dies passiert, aber was ist, wenn Ihre Ansicht nicht innerhalb von 1 Sekunde gemessen wird und Sie dies als ausführbar bezeichnen, um die Breite / Höhe der Ansicht zu ermitteln? Es würde immer noch 0 ergeben. Bearbeiten: Oh und übrigens, dass 1 in Millisekunden ist, also würde es mit Sicherheit fehlschlagen.
Alex
4

Ich war auch verloren um getMeasuredWidth()und getMeasuredHeight() getHeight()und getWidth()für eine lange Zeit .......... später fand ich , dass die Breite und die Höhe der Ansicht immer in onSizeChanged()ist der beste Weg , dies zu tun ........ Sie können dynamisch Erhalten Sie Ihre aktuelle Breite und aktuelle Höhe Ihrer Ansicht, indem Sie die überschreibenonSizeChanged( Methode) .

Vielleicht möchten Sie einen Blick darauf werfen, der ein ausgeklügeltes Code-Snippet enthält. Neuer Blog-Beitrag: So erhalten Sie die Abmessungen für Breite und Höhe einer benutzerdefinierten Ansicht (erweitert Ansicht) in Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html

Rakib
quelle
0

Sie können sowohl Position als auch Dimension der Ansicht auf dem Bildschirm anzeigen

 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
quelle
-1

funktioniert perfekt für mich:

 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
quelle