15

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 le edit history si vous êtes intéressé par l'ancienne version.OnSur mesure personnalisée: comment obtenir la largeur en fonction de la hauteur

Un parent RelativeLayout envoie MeasureSpecs à onMeasure de son point de vue de l'enfant méthode afin de voir la taille de l'enfant voudrait être. Cela se produit en plusieurs passes.

Mon point de vue personnalisé

J'ai un custom view. Lorsque 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 (pour autant que wrap_content ait été sélectionné pour la largeur). Ainsi, la largeur de la vue personnalisée dépend directement de ce que le parent dit que le maximum doit être.

enter image description here

Un enfant parent (inharmonieuse) Conversation

onMeasure passe 1

Le parent RelativeLayout dit mon avis, « Vous pouvez être une largeur up to900 et toute hauteur up to600 . Que dis-tu?" Mon point de vue dit: «Eh bien, à cette hauteur, je peux tout adapter avec une largeur de 100.Je vais prendre une largeur de 100 et une hauteur de 600."

onMeasure passe 2

Le parent RelativeLayout dit mon avis, « Vous me avez dit la dernière fois que vous vouliez une largeur de 100, donc Fixons que comme une largeur exact. Maintenant, en fonction de cette largeur, quel genre de hauteur voulez-vous? Tout up to500 est OK. "

"Hey!" Mon avis répond. "Si vous me donnez seulement une hauteur maximale de 500, alors 100 est trop étroit.J'ai besoin d'une largeur de pour cette hauteur.Mais bien, ayez votre chemin.Je ne vais pas enfreindre 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 découpée.

"Soupir", pense mon point de vue. "Pourquoi mon parent ne me laisse-t-il pas être plus large? Il y a beaucoup de place, peut-être que quelqu'un sur Stack Overflow peut me donner des conseils."

+0

Je pense que c'est une question de vos unités, s'il vous plaît envisager d'utiliser une seule unité dp ou px en largeur et en hauteur. – Mohamed

+0

@Mohamed, Mon exemple ici utilise uniquement px. – Suragch

+0

Lorsque j'utilise votre vue personnalisée à l'intérieur d'un LinearLayout, cela fonctionne correctement. Avez-vous essayé cela? – Krish

Répondre

6

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

D'abord, permettez-moi de dire que vous avez posé une grande question et Disposées très bien le problème ici est mon aller à une solution (deux fois!):

Il semble qu'il y ait beaucoup de choses avec onMeasure cela, en surface, n'a pas beaucoup de sens. Puisque tel est le cas, nous laisserons onMeasure courir comme il se doit et, à la fin, nous jugerons les limites de View en onLayout en réglant mStickyWidth à la nouvelle largeur minimale que nous accepterons. Dans onPreDraw, en utilisant un ViewTreeObserver.OnPreDrawListener, nous allons forcer une autre mise en page (requestLayout). De l'documentation (italique ajouté):

boolean onPreDraw()

méthode de rappel à invoquer lorsque l'arbre de vue est sur le point d'être dessiné. À ce stade, toutes les vues dans l'arbre ont été mesurées et étant donné une trame. Les clients peuvent utiliser ceci pour ajuster leurs limites de défilement ou même pour demander une nouvelle mise en page avant que le dessin ne se produise.

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

J'ai testé ceci 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; 
} 
+0

Je suis très heureux de voir une réponse qui résout réellement le problème. J'étais sur le point d'abandonner l'espoir. – Suragch

+0

C'est un problème intrigant - accrocher là. Quand je me bats sur le framework Android, le framework gagne généralement, mais nous pouvons sortir cette fois-ci. – Cheticamp

+1

J'ai testé cela sur MCVE et sur mon code de vue personnalisé. Les deux fonctionnent maintenant dans les situations que j'ai décrites dans ma question. – Suragch

1

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

Il est difficile de ne pas mettre en œuvre le comportement que vous voulez. Vous avez juste besoin de passer outre onMeasure et OnLayout dans votre vue personnalisée.

Dans onMeasure vous obtiendrez la hauteur maximale possible de votre vue personnalisée et appelez measure() pour les enfants en cycle.Après la mesure de l'enfant, obtenez la hauteur désirée de chaque enfant et calculez l'ajustement de l'enfant dans la colonne courante, sinon augmentez la vue personnalisée

Dans onLayout vous devez appeler layou t() pour toutes les vues enfants afin de leur attribuer des positions dans le parent. Cette position que vous avez calculée dans onMeasure avant.

+0

Dans ma 'onMeasure', je calcule la largeur de ma vue. Cependant, au moment où j'arrive à 'onLayout', la taille incorrecte a été sélectionnée par le parent' RelativeLayout' (pas dans toutes les situations mais spécifiquement quand il s'agit d'une autre vue 'layout_below') et le contenu vient à peine s'ajouter à la seconde colonne. certaine hauteur). Le code actuel est [ici] (https://github.com/suragch/mongol-library/blob/master/mongol-library/src/main/java/net/studymongolian/mongollibrary/MongolTextView.java) (ou dans le modifier l'historique de ma question). – Suragch