Un moyen autoritaire de remplacer onMeasure ()?

87

Quelle est la bonne façon de remplacer onMeasure ()? J'ai vu différentes approches. Par exemple, Professional Android Development utilise MeasureSpec pour calculer les dimensions, puis se termine par un appel à setMeasuredDimension (). Par exemple:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
}

D'autre part, selon cet article , la manière «correcte» est d'utiliser MeasureSpec, d'appeler setMeasuredDimensions (), suivi d' un appel à setLayoutParams () et de se terminer par un appel à super.onMeasure (). Par exemple:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
this.setLayoutParams(new *ParentLayoutType*.LayoutParams(parentWidth/2,parentHeight));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Alors, quelle est la bonne manière? Aucune des deux approches n'a fonctionné à 100% pour moi.

Je suppose que ce que je demande vraiment, c'est que quelqu'un connaît un tutoriel qui explique onMeasure (), la mise en page, les dimensions des vues enfants, etc.?

U Avalos
la source
15
avez-vous trouvé la réponse utile / correcte? pourquoi ne pas en faire la réponse?
superjos

Réponses:

68

Les autres solutions ne sont pas globales. Ils peuvent fonctionner dans certains cas et sont un bon point de départ, mais ils ne sont pas garantis de fonctionner.

Lorsque onMeasure est appelé, vous pouvez ou non avoir le droit de modifier la taille. Les valeurs transmises à votre onMeasure ( widthMeasureSpec, heightMeasureSpec) contiennent des informations sur ce que votre vue enfant est autorisée à faire. Actuellement, il existe trois valeurs:

  1. MeasureSpec.UNSPECIFIED - Vous pouvez être aussi grand que vous le souhaitez
  2. MeasureSpec.AT_MOST- Aussi grand que vous le souhaitez (jusqu'à la taille des spécifications), c'est parentWidthdans votre exemple.
  3. MeasureSpec.EXACTLY- Pas le choix. Parent a choisi.

Ceci est fait pour qu'Android puisse effectuer plusieurs passes pour trouver la bonne taille pour chaque élément, voir ici pour plus de détails.

Si vous ne suivez pas ces règles, votre approche n'est pas garantie de fonctionner.

Par exemple, si vous souhaitez vérifier si vous êtes autorisé à modifier la taille, vous pouvez effectuer les opérations suivantes:

final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;

En utilisant ces informations, vous saurez si vous pouvez modifier les valeurs comme dans votre code. Ou si vous devez faire quelque chose de différent. Un moyen rapide et facile de déterminer la taille souhaitée consiste à utiliser l'une des méthodes suivantes:

int resolutionSizeAndState (taille int, valeur int measureSpec, int childMeasuredState)

int resolutionSize (taille int, int measureSpec)

Alors que le premier n'est disponible que sur Honeycomb, le second est disponible sur toutes les versions.

Remarque: vous pouvez trouver cela resizeWidthou resizeHeightêtre toujours faux. J'ai trouvé que c'était le cas si je demandais MATCH_PARENT. J'ai pu résoudre ce problème en demandant WRAP_CONTENTsur ma mise en page parent puis pendant la phase NON SPÉCIFIÉE en demandant une taille de Integer.MAX_VALUE. Cela vous donne la taille maximale que votre parent autorise lors du prochain passage sur onMeasure.

Grimmace
la source
39

La documentation fait autorité en la matière: http://developer.android.com/guide/topics/ui/how-android-draws.html et http://developer.android.com/guide/topics/ui/custom -components.html

Pour résumer: à la fin de votre onMeasureméthode surchargée , vous devez appeler setMeasuredDimension.

Vous ne devriez pas appeler super.onMeasureaprès avoir appelé setMeasuredDimension, cela effacera simplement tout ce que vous avez défini. Dans certaines situations, vous souhaiterez peut-être appeler le super.onMeasurepremier, puis modifier les résultats en appelant setMeasuredDimension.

Ne pas appeler setLayoutParamsà onMeasure. La mise en page se produit dans un deuxième passage après la mesure.

satur9nine
la source
Mon expérience est que si je remplace onMeasure sans appeler super.onMeasure, OnLayout n'est pas appelé sur les vues enfants.
William Jockusch
2

Je pense que cela dépend du parent de ce que vous dépassez.

Par exemple, si vous étendez un ViewGroup (comme FrameLayout), lorsque vous avez mesuré la taille, vous devez appeler comme ci-dessous

super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));

parce que vous voudrez peut-être à ViewGroup pour faire un travail de repos (faire quelques trucs sur la vue enfant)

Si vous étendez une vue (comme ImageView), vous pouvez simplement appeler this.setMeasuredDimension(width, height);, car la classe parente fera simplement quelque chose comme vous l'avez fait habituellement.

En un mot, si vous voulez certaines fonctionnalités que votre classe parent offre gratuitement, vous devez appeler super.onMeasure()(passez généralement la spécification de mesure du mode MeasureSpec.EXACTLY), sinon l'appel this.setMeasuredDimension(width, height);suffit.

Yon
la source
1

voici comment j'ai résolu le problème:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        ....

        setMeasuredDimension( measuredWidth, measuredHeight );

        widthMeasureSpec = MeasureSpec.makeMeasureSpec( measuredWidth, MeasureSpec.EXACTLY );
        heightMeasureSpec = MeasureSpec.makeMeasureSpec( measuredHeight, MeasureSpec.EXACTLY);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

De plus, il était nécessaire pour le composant ViewPager

Yuli Reiri
la source
3
Ce n'est pas une bonne solution. super.onMeasureeffacera les dimensions définies à l'aide de setMeasuredDimension.
tomrozb
@tomrozb ce n'est pas humptydevelopers.com/2013/05/… et vous pouvez vérifier les sources Android
Yuli Reiri
@YuliReiri: Solution parfaite!
Shayan Tabatabaee
0

Si vous changez la taille des vues à l'intérieur de onMeasuretout ce dont vous avez besoin est l' setMeasuredDimensionappel. Si vous changez la taille en dehors de onMeasurevous devez appeler setLayoutParams. Par exemple, changer la taille d'une vue de texte lorsque le texte est modifié.

Kyle O.
la source
Vous pouvez toujours appeler setMinWidth () et setMaxWidth () et laisser le onMeasure le gérer. J'ai trouvé qu'il était préférable de ne pas appeler setLayoutParams depuis l'intérieur d'une classe View personnalisée. Il est vraiment utilisé pour les ViewGroups ou Activities pour remplacer le comportement de la vue enfant.
Dorrin
0

Dépend du contrôle que vous utilisez. Les instructions de la documentation fonctionnent pour certains contrôles (TextView, Button, ...), mais pas pour d'autres (LinearLayout, ...). La façon qui a très bien fonctionné pour moi était d'appeler le super une fois que j'aurais terminé. Basé sur l'article dans le lien ci-dessous.

http://humptydevelopers.blogspot.in/2013/05/android-view-overriding-onmeasure.html

Aviral
la source
-1

Je suppose que setLayoutParams et recalculer les mesures est une solution de contournement pour redimensionner correctement les vues enfants, car cela se fait généralement dans onMeasure de la classe dérivée.

Cependant, cela fonctionne rarement correctement (pour une raison quelconque ...), il vaut mieux appeler measureChildren (lors de la dérivation d'un ViewGroup) ou essayer quelque chose de similaire si nécessaire.

M. Schenk
la source
-5

vous pouvez prendre ce morceau de code comme exemple de onMeasure () ::

public class MyLayerLayout extends RelativeLayout {

    public MyLayerLayout(Context context) {
        super(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);

        int currentChildCount = getChildCount();
        for (int i = 0; i < currentChildCount; i++) {
            View currentChild = getChildAt(i);

            //code to find information

            int widthPercent = currentChildInfo.getWidth();
            int heightPercent = currentChildInfo.getHeight();

//considering we will pass height & width as percentage

            int myWidth = (int) Math.round(parentWidth * (widthPercent / 100.0));
            int myHeight = (int) Math.round(parentHeight * (heightPercent / 100.0));

//Considering we need to set horizontal & vertical position of the view in parent

            AlignmentTraitValue vAlign = currentChildInfo.getVerticalLocation() != null ? currentChildlayerInfo.getVerticalLocation() : currentChildAlignmentTraitValue.TOP;
            AlignmentTraitValue hAlign = currentChildInfo.getHorizontalLocation() != null ? currentChildlayerInfo.getHorizontalLocation() : currentChildAlignmentTraitValue.LEFT;
            int topPadding = 0;
            int leftPadding = 0;

            if (vAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                topPadding = (parentHeight - myHeight) / 2;
            } else if (vAlign.equals(currentChildAlignmentTraitValue.BOTTOM)) {
                topPadding = parentHeight - myHeight;
            }

            if (hAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                leftPadding = (parentWidth - myWidth) / 2;
            } else if (hAlign.equals(currentChildAlignmentTraitValue.RIGHT)) {
                leftPadding = parentWidth - myWidth;
            }
            LayoutParams myLayoutParams = new LayoutParams(myWidth, myHeight);
            currentChildLayoutParams.setMargins(leftPadding, topPadding, 0, 0);
            currentChild.setLayoutParams(myLayoutParams);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
Sayan
la source
6
Ce code est fondamentalement faux. Premièrement, il ne prend pas en compte le mode de mise en page (voir la réponse de Grimmace). C'est mauvais mais vous ne verrez pas l'effet de cela parce que vous appelez également super.onMeasure () à la toute fin, ce qui remplace les valeurs que vous avez définies (voir la réponse de satur9nine) et va à l'encontre du but de remplacer onMeasure ().
spaaarky21