Explication de la vue personnalisée onMeasure


316

J'ai essayé de faire un composant personnalisé. J'ai étendu la Viewclasse et fait du dessin dans une onDrawméthode redéfinie. Pourquoi ai-je besoin de passer outre onMeasure? Si je ne le faisais pas, tout semblait correct. Quelqu'un peut-il l'expliquer? Comment dois-je écrire ma onMeasureméthode? J'ai vu quelques tutoriels, mais chacun est un peu différent de l'autre. Parfois, ils appellent super.onMeasureà la fin, parfois ils utilisent setMeasuredDimensionet ne l'appellent pas. Où est la différence?

Après tout, je veux utiliser plusieurs exactement les mêmes composants. J'ai ajouté ces composants à mon XMLfichier, mais je ne sais pas quelle taille ils devraient avoir. Je veux définir sa position et sa taille plus tard (pourquoi je dois définir la taille onMeasuresi onDrawsi je le dessine, cela fonctionne également) dans la classe de composants personnalisés. Quand dois-je faire exactement cela?

Réponses:


735

onMeasure()est votre occasion de dire à Android la taille que vous souhaitez que votre vue personnalisée dépende des contraintes de mise en page fournies par le parent; c'est également l'occasion pour votre vue personnalisée d'apprendre quelles sont ces contraintes de disposition (au cas où vous voudriez vous comporter différemment dans une match_parentsituation que dans une wrap_contentsituation). Ces contraintes sont regroupées dans les MeasureSpecvaleurs transmises à la méthode. Voici une corrélation approximative des valeurs de mode:

  • EXACTEMENT signifie que la valeur layout_widthou a layout_heightété définie sur une valeur spécifique. Vous devriez probablement faire votre point de vue de cette taille. Cela peut également être déclenché lorsque match_parentest utilisé, pour définir la taille exactement à la vue parent (cela dépend de la disposition dans le cadre).
  • AT_MOST signifie généralement que la valeur layout_widthou a layout_heightété définie sur match_parentou wrap_contentlà où une taille maximale est nécessaire (cela dépend de la disposition dans le cadre), et la taille de la dimension parent est la valeur. Vous ne devriez pas être plus grand que cette taille.
  • NON SPÉCIFIÉ signifie généralement que la valeur layout_widthou a layout_heightété définie sur wrap_contentsans aucune restriction. Vous pouvez avoir la taille que vous souhaitez. Certaines dispositions utilisent également ce rappel pour déterminer la taille souhaitée avant de déterminer les spécifications pour vous transmettre à nouveau dans une deuxième demande de mesure.

Le contrat qui existe avec onMeasure()est - ce que setMeasuredDimension() DOIT être appelée à la fin de la taille que vous souhaitez que la vue soit. Cette méthode est appelée par toutes les implémentations du framework, y compris l'implémentation par défaut trouvée dans View, c'est pourquoi il est sûr d'appeler à la superplace si cela correspond à votre cas d'utilisation.

Certes, car le cadre applique une implémentation par défaut, il peut ne pas être nécessaire pour vous de remplacer cette méthode, mais vous pouvez voir un écrêtage dans les cas où l'espace d'affichage est plus petit que votre contenu si vous ne le faites pas et si vous disposez votre vue personnalisée avec wrap_contentdans les deux sens, votre vue peut ne pas apparaître du tout car le framework ne sait pas quelle est sa taille!

Généralement, si vous remplacez Viewet pas un autre widget existant, c'est probablement une bonne idée de fournir une implémentation, même si c'est aussi simple que quelque chose comme ceci:

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

J'espère que cela pourra aider.


1
Hé @Devunwired belle explication la meilleure que j'ai lue jusqu'à présent. Votre explication a répondu à beaucoup de questions que j'ai posées et a dissipé certains doutes, mais il en reste une qui est: si ma vue personnalisée est à l'intérieur d'un ViewGroup avec quelques autres vues (peu importe les types) que ViewGroup obtiendra à tous ses enfants un pour chacun une sonde pour leur contrainte LayoutParams et demandez à chaque enfant de le mesurer lui-même en fonction de leurs contraintes?
pharaon le

47
Notez que ce code ne fonctionnera pas si vous remplacez onMeasure d'une sous-classe ViewGroup. Vos sous-vues n'apparaîtront pas et auront toutes une taille de 0x0. Si vous devez remplacer onMeasure d'un ViewGroup personnalisé, modifiez widthMode, widthSize, heightMode et heightSize, compilez-les de nouveau pour mesurerSpecs à l'aide de MeasureSpec.makeMeasureSpec et transmettez les entiers résultants à super.onMeasure.
Alexey

1
Réponse fantastique. Notez que, selon la documentation de Google, il est de la responsabilité de View de gérer le remplissage.
jonstaff

4
Sur c ** p compliqué qui fait d'Android un système de mise en page douloureux avec lequel travailler. Ils auraient pu avoir juste getParent (). Get *** () ...
Oliver Dixon

2
Il existe des méthodes auxiliaires dans la Viewclasse, appelées resolveSizeAndStateet resolveSize, qui devraient faire ce que font les clauses «si» - je les ai trouvées utiles, surtout si vous devez souvent écrire ces IF.
stan0

5

en fait, votre réponse n'est pas complète car les valeurs dépendent également du conteneur d'emballage. En cas de dispositions relatives ou linéaires, les valeurs se comportent comme ceci:

  • EXACTEMENT match_parent est EXACTEMENT + taille du parent
  • AT_MOST wrap_content entraîne une AT_MOST MeasureSpec
  • NON SPÉCIFIÉ jamais déclenché

Dans le cas d'une vue à défilement horizontal, votre code fonctionnera.


57
Si vous pensez qu'une réponse ici est incomplète, veuillez y ajouter plutôt que de donner une réponse partielle.
Michaël

1
Bon onya pour avoir lié cela au fonctionnement des mises en page, mais dans mon cas onMeasure est appelé trois fois pour ma vue personnalisée. La vue en question avait une hauteur wrap_content et une largeur pondérée (largeur = 0, poids = 1). Le premier appel avait NON SPÉCIFIÉ / NON SPÉCIFIÉ, le second avait AT_MOST / EXACTEMENT et le troisième avait EXACTEMENT / EXACTEMENT.
William T. Mallard

0

Si vous n'avez pas besoin de changer quelque chose sur Mesure, vous n'avez absolument pas besoin de le remplacer.

Le code Devunwired (la réponse sélectionnée et la plus votée ici) est presque identique à ce que la mise en œuvre du SDK fait déjà pour vous (et j'ai vérifié - il l'avait fait depuis 2009).

Vous pouvez vérifier la méthode onMeasure ici :

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

Remplacer le code SDK à remplacer par le même code exact n'a aucun sens.

Cette pièce de doc officielle que les revendications « le onMeasure par défaut () sera toujours définir une taille de 100x100 » - est faux.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.