web-dev-qa-db-fra.com

explication de vue personnalisée onMeasure

J'ai essayé de faire des composants personnalisés. J'ai étendu la classe View et dessiné dans la méthode onDraw surchargée. Pourquoi dois-je remplacer onMeasure? Si je ne le faisais pas, tout se passerait bien. Quelqu'un peut-il l'expliquer? Comment dois-je écrire ma méthode onMeasure? 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 setMeasuredDimension sans l'appeler. Où est la différence?

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

304
sennin

onMeasure() est l'occasion pour vous de dire à Android à quel point vous souhaitez que votre affichage personnalisé soit dépendant des contraintes de présentation fournies par le parent; c'est également l'occasion pour votre vue personnalisée d'apprendre quelles sont ces contraintes de présentation (si vous voulez vous comporter différemment dans une situation match_parent que dans une situation wrap_content). Ces contraintes sont regroupées dans les valeurs MeasureSpec qui sont transmises à la méthode. Voici une corrélation approximative des valeurs de mode:

  • EXACTEMENT signifie que la valeur layout_width ou layout_height a été définie sur une valeur spécifique. Vous devriez probablement faire de votre point de vue cette taille. Cela peut également être déclenché lorsque match_parent est utilisé, pour définir la taille exactement de la vue parente (ceci dépend de la présentation dans le cadre).
  • AT_MOST signifie généralement que la valeur layout_width ou layout_height a été définie sur match_parent ou wrap_content où une taille maximale est requise (cette disposition dépend de la 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_width ou layout_height a été définie sur wrap_content sans aucune restriction. Vous pouvez avoir la taille que vous souhaitez. Certaines présentations utilisent également ce rappel pour déterminer la taille souhaitée avant de déterminer les spécifications à appliquer pour vous transmettre à nouveau dans une deuxième demande de mesure.

Le contrat qui existe avec onMeasure() est que setMeasuredDimension()DOIT être appelé à la fin avec la taille souhaitée pour la vue. Cette méthode est appelée par toutes les implémentations d'infrastructure, y compris l'implémentation par défaut trouvée dans View. C'est pourquoi il est prudent d'appeler super si cela convient à votre cas d'utilisation.

Certes, du fait que le framework applique une implémentation par défaut, il peut ne pas être nécessaire de remplacer cette méthode, mais vous risquez de voir des coupures dans les cas où l'espace d'affichage est plus petit que votre contenu. Si vous ne le faites pas, et vue personnalisée avec wrap_content dans les deux sens, votre vue risque de ne pas s'afficher du tout car le cadre ne sait pas quelle est sa taille!

Généralement, si vous écrasez View et non un autre widget existant, il est probablement judicieux de fournir une implémentation, même si elle est aussi simple que 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 ça t'as aidé.

707
Devunwired

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

  • EXACTEMENT match_parent est EXACTEMENT + taille du parent
  • AT_MOST wrap_content génère une AT_MOST MeasureSpec
  • NON SPÉCIFIÉ jamais déclenché

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

4
Florian

Si vous n'avez pas besoin de changer quelque chose surMeasure, vous n'avez absolument pas besoin de l'annuler.

Le code de 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é - c'est ce qu'il fait depuis 2009).

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

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 par le même code n'a aucun sens.

Ce document officiel du document qui déclare que "la valeur par défaut onMeasure () définira toujours une taille de 100x100" - est incorrect.

0
David Refaeli