web-dev-qa-db-fra.com

Vue personnalisée surMesure: comment obtenir la largeur en fonction de la hauteur

La version précédente de ma question était trop verbeuse. Les gens ne pouvaient pas le comprendre, donc ce qui suit est une réécriture complète. Voir modifier l'historique si vous êtes intéressé par l'ancienne version.

Un parent RelativeLayout envoie MeasureSpecs à la méthode onMeasure de sa vue enfant afin de voir sa taille. Cela se produit en plusieurs passes.

Ma vue personnalisée

J'ai un vue personnalisée . À mesure que le contenu de la vue augmente, la hauteur de la vue augmente. Lorsque la vue atteint la hauteur maximale autorisée par le parent, la largeur de la vue augmente pour tout contenu supplémentaire (tant que wrap_content a été sélectionné pour la largeur). Ainsi, la largeur de la vue personnalisée dépend directement de ce que le parent dit que la hauteur maximale doit être.

enter image description here

Une conversation parent-enfant (inharmonieuse)

onMeasure passe 1

Le parent RelativeLayout dit à ma vue: "Vous pouvez avoir n'importe quelle largeur jusqu'à900 et n'importe quelle hauteur jusqu'à600. Que dis-tu?"

Ma vue dit: "Eh bien, à cette hauteur, je peux tout adapter avec une largeur de 100. Je vais donc prendre une largeur de 100 et une hauteur de 600. "

onMeasure passe 2

Le parent RelativeLayout dit à mon avis: "Vous m'avez dit la dernière fois que vous vouliez une largeur de 100, définissons cela comme une largeur exacte . Maintenant, sur la base de cette largeur, quel type de hauteur aimeriez-vous? N'importe quoi jusqu'à500 est OK. "

"Hey!" mon avis répond. "Si vous me donnez seulement une hauteur maximale de 500, puis 100 est trop étroit. J'ai besoin d'une largeur de 200 pour cette hauteur. Mais bon, faites-le à votre façon. Je ne violerai pas les règles (encore...). Je vais prendre une largeur de 100 et une hauteur de 500. "

Résultat final

Le parent RelativeLayout attribue à la vue une taille finale de 100 pour la largeur et 500 pour la hauteur. Ceci est bien sûr trop étroit pour la vue et une partie du contenu est tronquée.

"Soupir", pense mon point de vue. "Pourquoi mes parents ne me laissent-ils pas élargir? Il y a beaucoup de place. Peut-être que quelqu'un sur Stack Overflow peut me donner quelques conseils."

17
Suragch

Mise à jour: Code modifié pour corriger certaines choses.

Tout d'abord, permettez-moi de dire que vous avez posé une excellente question et posé très bien le problème (deux fois!) Voici ma solution:

Il semble qu'il se passe beaucoup de choses avec onMeasure qui, à première vue, n'a pas beaucoup de sens. Comme c'est le cas, nous laisserons onMeasure s'exécuter comme il le fera et, à la fin, nous jugerons les limites de View dans onLayouten définissant mStickyWidth sur nouvelle largeur minimale que nous accepterons. Dans onPreDraw, en utilisant un ViewTreeObserver.OnPreDrawListener, Nous forcerons une autre disposition (requestLayout). De la documentation (emphase ajoutée):

boolean onPreDraw ()

Méthode de rappel à invoquer lorsque l'arborescence de la vue est sur le point d'être dessinée. À ce stade, toutes les vues de l'arborescence ont été mesurées et dotées d'un cadre. Les clients peuvent l'utiliser pour ajuster leurs limites de défilement ou même pour demander une nouvelle mise en page avant le dessin .

La nouvelle largeur minimale définie dans onLayout sera désormais appliquée par onMeasure qui est désormais plus intelligent sur ce qui est possible.

J'ai testé cela avec votre exemple de code et cela semble fonctionner correctement. Il faudra beaucoup plus de tests. Il peut y avoir d'autres façons de le faire, mais c'est l'essentiel de l'approche.

CustomView.Java

import Android.content.Context;
import Android.util.AttributeSet;
import Android.util.Log;
import Android.view.View;
import Android.view.ViewTreeObserver;

public class CustomView extends View
        implements ViewTreeObserver.OnPreDrawListener {
    private int mStickyWidth = STICKY_WIDTH_UNDEFINED;

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

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        logMeasureSpecs(widthMeasureSpec, heightMeasureSpec);
        int desiredHeight = 10000; // some value that is too high for the screen
        int desiredWidth;

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;

        // Height
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            height = Math.min(desiredHeight, heightSize);
        } else {
            height = desiredHeight;
        }

        // Width
        if (mStickyWidth != STICKY_WIDTH_UNDEFINED) {
            // This is the second time through layout and we are trying renogitiate a greater
            // width (mStickyWidth) without breaking the contract with the View.
            desiredWidth = mStickyWidth;
        } else if (height > BREAK_HEIGHT) { // a number between onMeasure's two final height requirements
            desiredWidth = ARBITRARY_WIDTH_LESSER; // arbitrary number
        } else {
            desiredWidth = ARBITRARY_WIDTH_GREATER; // arbitrary number
        }

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            width = Math.min(desiredWidth, widthSize);
        } else {
            width = desiredWidth;
        }

        Log.d(TAG, "setMeasuredDimension(" + width + ", " + height + ")");
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        int w = right - left;
        int h = bottom - top;

        super.onLayout(changed, left, top, right, bottom);
        // Here we need to determine if the width has been unnecessarily constrained.
        // We will try for a re-fit only once. If the sticky width is defined, we have
        // already tried to re-fit once, so we are not going to have another go at it since it
        // will (probably) have the same result.
        if (h <= BREAK_HEIGHT && (w < ARBITRARY_WIDTH_GREATER)
                && (mStickyWidth == STICKY_WIDTH_UNDEFINED)) {
            mStickyWidth = ARBITRARY_WIDTH_GREATER;
            getViewTreeObserver().addOnPreDrawListener(this);
        } else {
            mStickyWidth = STICKY_WIDTH_UNDEFINED;
        }
        Log.d(TAG, ">>>>onLayout: w=" + w + " h=" + h + " mStickyWidth=" + mStickyWidth);
    }

    @Override
    public boolean onPreDraw() {
        getViewTreeObserver().removeOnPreDrawListener(this);
        if (mStickyWidth == STICKY_WIDTH_UNDEFINED) { // Happy with the selected width.
            return true;
        }

        Log.d(TAG, ">>>>onPreDraw() requesting new layout");
        requestLayout();
        return false;
    }

    protected void logMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        String measureSpecHeight;
        String measureSpecWidth;

        if (heightMode == MeasureSpec.EXACTLY) {
            measureSpecHeight = "EXACTLY";
        } else if (heightMode == MeasureSpec.AT_MOST) {
            measureSpecHeight = "AT_MOST";
        } else {
            measureSpecHeight = "UNSPECIFIED";
        }

        if (widthMode == MeasureSpec.EXACTLY) {
            measureSpecWidth = "EXACTLY";
        } else if (widthMode == MeasureSpec.AT_MOST) {
            measureSpecWidth = "AT_MOST";
        } else {
            measureSpecWidth = "UNSPECIFIED";
        }

        Log.d(TAG, "Width: " + measureSpecWidth + ", " + widthSize + " Height: "
                + measureSpecHeight + ", " + heightSize);
    }

    private static final String TAG = "CustomView";
    private static final int STICKY_WIDTH_UNDEFINED = -1;
    private static final int BREAK_HEIGHT = 1950;
    private static final int ARBITRARY_WIDTH_LESSER = 200;
    private static final int ARBITRARY_WIDTH_GREATER = 800;
}
9
Cheticamp

Pour créer une mise en page personnalisée, vous devez lire et comprendre cet article https://developer.Android.com/guide/topics/ui/how-Android-draws.html

Il n'est pas difficile de mettre en œuvre le comportement que vous souhaitez. Il vous suffit de remplacer onMeasure et onLayout dans votre vue personnalisée.

Dans onMeasure, vous obtiendrez la hauteur maximale possible de votre vue personnalisée et de la mesure d'appel () pour les enfants en cycle. Après la mesure de l'enfant, obtenez la hauteur souhaitée pour chaque enfant et calculez si l'enfant correspond à la colonne actuelle ou non, sinon augmentez la vue personnalisée pour la nouvelle colonne.

Dans onLayout, vous devez appeler layout () pour toutes les vues enfant afin de définir leur position dans le parent. Ces positions que vous avez calculées dans onMeasure auparavant.

1
Nik