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 MeasureSpec
s à la méthode onMeasure
de sa vue enfant afin de voir sa taille. Cela se produit en plusieurs passes.
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.
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."
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 onLayout
en 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;
}
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.