Determinazione delle dimensioni di una visualizzazione Android in fase di esecuzione


123

Sto cercando di applicare un'animazione a una vista nella mia app Android dopo aver creato la mia attività. Per fare ciò, è necessario determinare la dimensione corrente della vista e quindi impostare un'animazione per ridimensionare dalla dimensione corrente alla nuova dimensione. Questa parte deve essere eseguita in fase di runtime, poiché la vista scala a dimensioni diverse a seconda dell'input dell'utente. Il mio layout è definito in XML.

Sembra un compito facile, e ci sono molte domande SO riguardo a questo anche se nessuna ha risolto il mio problema, ovviamente. Quindi forse mi manca qualcosa di ovvio. Ottengo un controllo della mia vista da:

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

Questo funziona bene, ma quando si chiama getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().width, ecc, tutti di ritorno 0. Ho anche provato manualmente chiamando measure()sulla vista seguita da una chiamata a getMeasuredWidth(), ma che non ha alcun effetto.

Ho provato a chiamare questi metodi e ispezionare l'oggetto nel debugger nella mia attività onCreate()e in onPostCreate(). Come posso calcolare le dimensioni esatte di questa vista in fase di esecuzione?


1
Oh, devo anche notare che la vista stessa non ha sicuramente 0 larghezza / altezza. Appare sullo schermo benissimo.
Nik Reiman,

Puoi pubblicare il tag <ImageView ... /> dal layout xml?
fhucho

Ho lottato con molte soluzioni SO a questo problema fino a quando non mi sono reso conto che nel mio caso le dimensioni della View corrispondevano allo schermo fisico (la mia app è "immersiva" e la View e tutte le larghezze e altezze dei suoi genitori sono impostate su match_parent). In questo caso, una soluzione più semplice che può essere tranquillamente chiamata anche prima che la View sia disegnata (ad esempio, dal metodo onCreate () della tua Activity) è semplicemente quella di usare Display.getSize (); vedi stackoverflow.com/a/30929599/5025060 per i dettagli. So che questo non è ciò che @NikReiman ha chiesto, lasciando solo una nota a coloro che potrebbero essere in grado di utilizzare questo metodo più semplice.
CODICE REaD

Risposte:


180

Utilizzare ViewTreeObserver sulla vista per attendere il primo layout. Solo dopo il primo layout getWidth () / getHeight () / getMeasuredWidth () / getMeasuredHeight () funzionerà.

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

21
Fai attenzione a questa risposta perché verrà chiamata continuamente se la vista sottostante è un video / superficie
Kevin Parker

7
Puoi approfondire questo @Kevin? Due cose, prima di tutto, poiché è un ascoltatore, mi aspetterei una singola richiamata, e successivamente, non appena otteniamo la prima richiamata, rimuoviamo l'ascoltatore. Ho dimenticato qualcosa?
Vikram Bodicherla

4
Prima di chiamare qualsiasi metodo ViewTreeObserver, si consiglia di controllare isAlive (): if (view.getViewTreeObserver (). IsAlive ()) {view.getViewTreeObserver (). RemoveGlobalOnLayoutListener (this); }
Peter Tran

4
qualche alternativa a removeGlobalOnLayoutListener(this);? perché è deprecato.
Sibbs Gambling

7
@FarticlePilter, usa invece removeOnGlobalLayoutListener (). È menzionato nei documenti.
Vikram Bodicherla

64

Esistono in realtà più soluzioni, a seconda dello scenario:

  1. Il metodo sicuro, funzionerà appena prima di disegnare la vista, dopo che la fase di layout è terminata:
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); 
}

Utilizzo del campione:

    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 alcuni casi, è sufficiente misurare manualmente le dimensioni della vista:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth(); 
int height=view.getMeasuredHeight();

Se conosci le dimensioni del contenitore:

    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. se hai una visualizzazione personalizzata che hai esteso, puoi ottenere le sue dimensioni con il metodo "onMeasure", ma penso che funzioni bene solo in alcuni casi:
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. Se scrivi in ​​Kotlin, puoi usare la funzione successiva, che dietro le quinte funziona esattamente come runJustBeforeBeingDrawnho scritto:

    view.doOnPreDraw { actionToBeTriggered() }

    Nota che devi aggiungerlo a gradle (trovato tramite qui ):

    implementation 'androidx.core:core-ktx:#.#'

1
Sembra che la soluzione n. 1 non funzionerà sempre OnPreDrawListener. Caso testato quando si desidera eseguire codice da Activity onCreate()e immediatamente dall'app in background. Facile da ripetere soprattutto su dispositivi più lenti. Se sostituisci OnPreDrawListenercon OnGlobalLayoutListener- non vedrai lo stesso problema.
interrogante

@questioner Non capisco, ma sono felice che ti abbia aiutato alla fine.
sviluppatore Android

Anche se di solito uso ViewTreeObservero post, nel mio caso il numero 2 ha aiutato ( view.measure).
CoolMind

3
@CoolMind Puoi anche usare un nuovo metodo, nel caso in cui usi Kotlin. Risposta aggiornata per includerla anche.
sviluppatore Android

perché kotlin ha funzioni aggiuntive per questo? google sono pazzi, questa lingua è inutile (dovrebbe rimanere con java), è molto più vecchia di swift ma solo swift ha ottenuto abbastanza popolarità tiobe.com/tiobe-index (swift 12 posizione e kotlin 38)
user924

18

Stai chiamando getWidth()prima che la visuale sia effettivamente visualizzata sullo schermo?

Un errore comune commesso dai nuovi sviluppatori Android è quello di utilizzare la larghezza e l'altezza di una vista all'interno del suo costruttore. Quando viene chiamato il costruttore di una vista, Android non sa ancora quanto sarà grande la vista, quindi le dimensioni sono impostate su zero. Le dimensioni reali vengono calcolate durante la fase di layout, che avviene dopo la costruzione ma prima di disegnare qualsiasi cosa. È possibile utilizzare il onSizeChanged()metodo per ricevere una notifica dei valori quando sono noti oppure è possibile utilizzare i metodi getWidth()e in un getHeight()secondo momento, ad esempio nel onDraw()metodo.


2
Quindi questo significa che devo sovrascrivere ImageView solo per catturare l' onSizeChange()evento per conoscere la dimensione reale? Sembra un po 'eccessivo ... anche se a questo punto sono disperato solo per far funzionare il codice, quindi vale la pena provare.
Nik Reiman,

Stavo pensando più come aspettare che il tuo onCreate () finisca nella tua attività.
Mark B

1
Come accennato, ho provato a inserire questo codice in onPostCreate()cui viene eseguito dopo che la vista è stata completamente creata e avviata.
Nik Reiman

1
Da dove viene il testo citato?
Intrications

1
questa citazione per la visualizzazione personalizzata, non pubblicarla per tale risposta quando chiediamo informazioni sulle dimensioni della visualizzazione in generale
user924

17

Sulla base del consiglio di @ mbaird, ho trovato una soluzione praticabile sottoclassando la ImageViewclasse e sostituendola onLayout(). Ho quindi creato un'interfaccia osservatore che la mia attività ha implementato e passato un riferimento a se stessa alla classe, che le ha permesso di dire all'attività quando era effettivamente terminato il dimensionamento.

Non sono convinto al 100% che questa sia la soluzione migliore (quindi non sto ancora contrassegnando questa risposta come corretta), ma funziona e secondo la documentazione è la prima volta che si riesce a trovare la dimensione effettiva di una vista.


Buono a sapersi. Se trovo qualcosa che ti permetta di aggirare la necessità di sottoclassare la vista, lo posterò qui. Sono un po 'sorpreso che onPostCreate () non abbia funzionato.
Mark B

Bene, ho provato questo e sembra funzionare. Solo che non è proprio la soluzione migliore poiché questo metodo viene chiamato frequentemente non solo una volta all'inizio. :(
Tobias Reich

10

Ecco il codice per ottenere il layout sovrascrivendo una vista se API <11 (API 11 include la funzione View.OnLayoutChangedListener):

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


5

Usa il codice sottostante, fornisce la dimensione della vista.

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

5

Questo funziona per me nel mio 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);

4
Non è necessario postDelayed quando è in un listener di clic. Non è possibile fare clic su nulla finché le visualizzazioni non si sono misurate e sono apparse sullo schermo. Quindi saranno già stati misurati quando sarai in grado di cliccarci sopra.
dopo il

Questo codice è sbagliato. Il ritardo di pubblicazione non garantisce che nel tick successivo la tua visualizzazione venga misurata.
Ian Wong

Consiglierei di non utilizzare postDelayed (). È improbabile che accada, ma cosa succede se la tua vista non viene misurata entro 1 secondo e lo chiami eseguibile per ottenere la larghezza / altezza della vista. Risulterebbe ancora 0. Modifica: Oh e btw che 1 è in millisecondi, quindi fallirebbe di sicuro.
Alex

4

Sono stato anche perso in giro getMeasuredWidth()ed getMeasuredHeight() getHeight()e getWidth()per un lungo periodo .......... più tardi ho scoperto che ottenere larghezza e l'altezza della vista in onSizeChanged()è il modo migliore per farlo ........ è possibile in modo dinamico ottieni la larghezza ATTUALE e l'altezza CORRENTE della tua vista sovrascrivendo il onSizeChanged(metodo).

potrei voler dare un'occhiata a questo che ha uno snippet di codice elaborato. Nuovo post sul blog: come ottenere le dimensioni di larghezza e altezza di una vista personalizzata (estende la vista) in Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html


0

È possibile ottenere sia la posizione che la dimensione della vista sullo schermo

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

        }
    });
}

-1

funziona perfettamente per me:

 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;
        }
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.