344 votes

Explication de la vue personnalisée onMeasure

J'ai essayé de créer un composant personnalisé. J'ai étendu View et faire un peu de dessin dans onDraw méthode dérogatoire. Pourquoi j'ai besoin de surcharger onMeasure ? Si je ne l'ai pas fait, tout semble correct. Quelqu'un peut-il l'expliquer ? Comment dois-je écrire mon onMeasure méthode ? J'ai vu quelques tutoriels, mais ils sont tous un peu différents les uns des autres. Parfois, ils appellent super.onMeasure à la fin, parfois ils utilisent setMeasuredDimension et ne l'a pas appelé. Où est la différence ?

Après tout, je veux utiliser plusieurs composants exactement identiques. J'ai ajouté ces composants à mon XML mais je ne sais pas quelle taille ils doivent avoir. Je veux définir sa position et sa taille ultérieurement (pourquoi je dois définir la taille dans le fichier onMeasure si en onDraw lorsque je le dessine, il fonctionne également) dans la classe du composant personnalisé. Quand exactement dois-je faire cela ?

797voto

Devunwired Points 27023

onMeasure() est l'occasion pour vous d'indiquer à Android la taille de votre vue personnalisée en fonction 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 mise en page (au cas où vous voudriez vous comporter différemment dans une match_parent situation qu'une wrap_content situation). Ces contraintes sont regroupées dans le MeasureSpec qui sont transmises à la méthode. Voici une corrélation approximative des valeurs de la méthode :

  • EXACTEMENT signifie que layout_width o layout_height a été fixé à une valeur spécifique. Vous devriez probablement donner à votre vue cette taille. Ceci peut également être déclenché lorsque match_parent est utilisé pour définir la taille exacte de la vue parent (cela dépend de la mise en page dans le cadre).
  • AT_MOST signifie généralement que le layout_width o layout_height a été fixé à match_parent o wrap_content 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 devez pas dépasser cette taille.
  • NON SPÉCIFIÉ signifie généralement que le layout_width o layout_height a été fixé à wrap_content sans aucune restriction. Vous pouvez être de la taille que vous souhaitez. Certaines mises en page utilisent également ce rappel pour déterminer votre taille souhaitée avant de déterminer les spécifications à vous passer à nouveau dans une deuxième demande de mesure.

Le contrat qui existe avec onMeasure() c'est que setMeasuredDimension() MUST être appelé à la fin avec la taille que vous souhaitez pour la vue. Cette méthode est appelée par toutes les implémentations du framework, y compris l'implémentation par défaut trouvée dans le fichier View c'est pourquoi il est prudent d'appeler super à la place, si cela correspond à votre cas d'utilisation.

Bien sûr, comme le framework applique une implémentation par défaut, il n'est peut-être pas nécessaire de remplacer cette méthode, mais vous risquez de voir des coupures dans les cas où l'espace de la vue est plus petit que votre contenu si vous ne le faites pas, et si vous disposez votre vue personnalisée avec wrap_content dans les deux sens, il se peut que votre vue n'apparaisse pas du tout car le cadre ne sait pas quelle est sa taille !

En général, si vous remplacez View et 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 vous aidera.

1 votes

Hey @Devunwired belle explication, la meilleure que j'ai lue jusqu'à présent. Votre explication a répondu à beaucoup de questions que j'avais et a levé certains doutes, mais il en reste encore un qui est : Si ma vue personnalisée est à l'intérieur d'un ViewGroup avec d'autres vues (peu importe le type), ce ViewGroup obtiendra tous ses enfants et pour chacun d'eux, il cherchera leur contrainte LayoutParams et demandera à chaque enfant de se mesurer en fonction de leurs contraintes ?

2 votes

Oui, c'est ce que le measureChildren() méthode de ViewGroup fait pendant le processus de mesure/de mise en page.

53 votes

Notez que ce code ne fonctionnera pas si vous surchargez onMeasure d'une sous-classe ViewGroup. Vos sous-vues n'apparaîtront pas et auront toutes une taille de 0x0. Si vous devez surcharger onMeasure d'un ViewGroup personnalisé, changez widthMode, widthSize, heightMode et heightSize, compilez-les en measureSpecs en utilisant MeasureSpec.makeMeasureSpec et passez les entiers résultants à super.onMeasure.

5voto

David Refaeli Points 732

Si vous n'avez pas besoin de modifier quelque chose surMeasure - il n'est absolument pas nécessaire de le remplacer.

Le code Devunwired (la réponse sélectionnée et la plus votée ici) est presque identique à ce que l'implémentation du SDK fait déjà pour vous (et j'ai vérifié - il le faisait 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 du SDK par le même code n'a aucun sens.

Ce site pièce officielle du doc qui affirme que "la fonction onMeasure() par défaut définira toujours une taille de 100x100" - est fausse.

5voto

Florian Points 328

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

  • EXACTEMENT match_parent est EXACTEMENT + taille du parent
  • AT_MOST wrap_content donne lieu à un AT_MOST MeasureSpec
  • NON SPÉCIFIÉ jamais déclenché

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

59 votes

Si vous estimez qu'une réponse donnée ici est incomplète, veuillez la compléter plutôt que de donner une réponse partielle.

1 votes

C'est bien d'avoir fait le lien avec le 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 (width = 0, weight = 1). Le premier appel avait UNSPECIFIED / UNSPECIFIED, le deuxième avait AT_MOST / EXACTLY et le troisième avait EXACTLY / EXACTLY.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X