onMeasure spiegazione vista personalizzata


316

Ho provato a fare un componente personalizzato. Ho esteso la Viewlezione e ho fatto qualche disegno con onDrawmetodo ignorato. Perché devo eseguire l'override onMeasure? In caso contrario, tutto visto per essere giusto. Qualcuno può spiegarlo? Come devo scrivere il mio onMeasuremetodo? Ho visto tutorial di coppia, ma ognuno è un po 'diverso dall'altro. A volte chiamano super.onMeasurealla fine, a volte usano setMeasuredDimensione non lo chiamano. Dov'è una differenza?

Dopo tutto, voglio usare diversi componenti esattamente uguali. Ho aggiunto quei componenti al mio XMLfile, ma non so quanto dovrebbero essere grandi. Voglio impostare la sua posizione e dimensione in un secondo momento (perché devo impostare la dimensione onMeasurese in onDrawquando la disegno, funziona anche) nella classe di componenti personalizzata. Quando esattamente devo farlo?

Risposte:


735

onMeasure()è la tua occasione per dire ad Android quanto vuoi che la tua vista personalizzata dipenda dai vincoli di layout forniti dal genitore; è anche l'opportunità della tua vista personalizzata di imparare quali sono questi vincoli di layout (nel caso in cui tu voglia comportarti diversamente in una match_parentsituazione rispetto a una wrap_contentsituazione). Questi vincoli vengono impacchettati nei MeasureSpecvalori passati nel metodo. Ecco una correlazione approssimativa dei valori della modalità:

  • ESATTAMENTE significa che il valore layout_widtho è layout_heightstato impostato su un valore specifico. Probabilmente dovresti rendere la tua vista di queste dimensioni. Questo può anche essere attivato quando match_parentviene utilizzato, per impostare le dimensioni esattamente sulla vista padre (dipende dal layout nel framework).
  • AT_MOST indica in genere che il valore layout_widtho è layout_heightstato impostato su match_parento wrap_contentdove è necessaria una dimensione massima (dipende dal layout nel framework) e la dimensione della dimensione padre è il valore. Non dovresti essere più grande di questa dimensione.
  • In genere, indica che il valore layout_widtho è layout_heightstato impostato su wrap_contentsenza restrizioni. Puoi avere qualsiasi dimensione desideri. Alcuni layout usano anche questo callback per capire la dimensione desiderata prima di determinare quali specifiche passano effettivamente di nuovo in una seconda richiesta di misura.

Il contratto che esiste onMeasure()è che setMeasuredDimension() DEVE essere chiamato alla fine con la dimensione che si desidera che la vista sia. Questo metodo viene chiamato da tutte le implementazioni del framework, inclusa l'implementazione predefinita trovata in View, motivo per cui è sicuro chiamare superinvece se si adatta al tuo caso d'uso.

Certo, poiché il framework applica un'implementazione predefinita, potrebbe non essere necessario ignorare questo metodo, ma è possibile che si verifichi il clipping nei casi in cui lo spazio di visualizzazione è più piccolo del contenuto se non lo si fa e se si imposta il proprio vista personalizzata con wrap_contentin entrambe le direzioni, la vista potrebbe non apparire affatto perché il framework non sa quanto sia grande!

In generale, se si esegue l'override Viewe non un altro widget esistente, è probabilmente una buona idea fornire un'implementazione, anche se è semplice come qualcosa del genere:

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

Spero che aiuti.


1
Ehi @Devunwired bella spiegazione la migliore che ho letto finora. La tua spiegazione ha risposto a molte domande che ho avuto e chiarito alcuni dubbi, ma rimane ancora una cosa che è: se la mia vista personalizzata è all'interno di un ViewGroup insieme ad alcune altre Viste (non importa quali tipi) che ViewGroup otterrà a tutti i suoi figli un per ciascuna sonda per il loro vincolo LayoutParams e chiedere a ciascun bambino di misurarsi da sé in base ai propri vincoli?
faraone

47
Si noti che questo codice non funziona se si esegue l'override di Misura di qualsiasi sottoclasse ViewGroup. Le visualizzazioni secondarie non verranno visualizzate e avranno tutte una dimensione di 0x0. Se devi sovrascrivere onMeasure di un ViewGroup personalizzato, cambia larghezzaMode, widthSize, heightMode e heightSize, compilale di nuovo in measureSpecs con MeasureSpec.makeMeasureSpec e passa gli interi risultanti a super.onMeasure.
Alexey,

1
Risposta fantastica. Si noti che, secondo la documentazione di Google, è responsabilità della visualizzazione gestire l'imbottitura.
jonstaff,

4
Oltre complicate c ** p che rendono Android un sistema di layout doloroso con cui lavorare. Avrebbero potuto avere getParent (). Get *** () ...
Oliver Dixon,

2
Esistono metodi helper nella Viewclasse, chiamati resolveSizeAndStatee resolveSize, che dovrebbero fare ciò che fanno le clausole 'if': li ho trovati utili, specialmente se devi scrivere spesso questi IF.
Stan0,

5

in realtà, la tua risposta non è completa in quanto i valori dipendono anche dal contenitore di wrapping. Nel caso di layout relativi o lineari, i valori si comportano così:

  • ESATTAMENTE match_parent è esattamente + dimensione del genitore
  • AT_MOST wrap_content genera un AT_MOST MeasureSpec
  • SPECIFICATO mai innescato

Nel caso di una vista di scorrimento orizzontale, il codice funzionerà.


57
Se ritieni che una risposta qui sia incompleta, ti preghiamo di aggiungerla invece di dare una risposta parziale.
Michaël

1
Ottimo per collegarlo a come funzionano i layout, ma nel mio caso onMeasure viene chiamato tre volte per la mia vista personalizzata. La vista in questione aveva un'altezza wrap_content e una larghezza ponderata (larghezza = 0, peso = 1). La prima chiamata aveva NON / NON, la seconda aveva AT_MOST / EXACTLY e la terza aveva EXACTLY / EXACTLY.
William T. Mallard,

0

Se non è necessario modificare qualcosa su Misura, non è assolutamente necessario sostituirlo.

Il codice Devunwired (la risposta selezionata e più votata qui) è quasi identico a quello che l'implementazione dell'SDK fa già per te (e ho verificato - lo aveva fatto dal 2009).

Puoi controllare il metodo onMeasure qui :

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

Sostituire il codice SDK da sostituire con lo stesso codice esatto non ha senso.

Questo documento ufficiale che afferma "l'impostazione predefinita onMeasure () imposterà sempre una dimensione di 100x100" - è errato.

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.