que - manual de programacion android pdf
OnMeasure de vista personalizada: cómo obtener ancho basado en altura (2)
La versión anterior de mi pregunta era demasiado prolija. La gente no podía entenderlo, así que lo siguiente es una reescritura completa. Vea el historial de edición si está interesado en la versión anterior.
Un padre de RelativeLayout
envía MeasureSpecs al método onMeasure
su vista onMeasure
para ver qué tan grande le gustaría al niño. Esto ocurre en varios pases.
Mi vista personalizada
Tengo una vista personalizada . A medida que aumenta el contenido de la vista, aumenta la altura de la vista. Cuando la vista alcanza la altura máxima que el padre permitirá, el ancho de la vista aumenta para cualquier contenido adicional (siempre que se wrap_content
seleccionado wrap_content
para el ancho). Por lo tanto, el ancho de la vista personalizada depende directamente de lo que el padre diga que debe ser la altura máxima.
Una conversación entre padres e hijos (inarmónico)
onMeasure
paso 1
El padre de RelativeLayout
dice a mi vista, "Puedes tener cualquier ancho hasta 900
y cualquier altura hasta 600
¿Qué dices?"
Mi opinión dice: "Bueno, a esa altura, puedo colocar todo con un ancho de 100
Así que tomaré un ancho de 100
y una altura de 600
".
paso de onMeasure
2
El padre de RelativeLayout
dice a mi vista, "la última vez que me dijiste que querías un ancho de 100
, así que vamos a establecerlo como un ancho exact . Ahora, según ese ancho, ¿qué tipo de altura te gustaría? Cualquier cosa hasta 500
es DE ACUERDO."
"¡Oye!" Mi opinión responde. "Si solo me das una altura máxima de 500
, entonces 100
es demasiado estrecho. Necesito un ancho de 200
para esa altura. Pero bueno, hazlo a tu manera. No romperé las reglas (aún ... ) . Tomaré un ancho de 100
y una altura de 500
".
Resultado final
El padre RelativeLayout
asigna a la vista un tamaño final de 100
para el ancho y 500
para la altura. Por supuesto, esto es demasiado limitado para la vista y parte del contenido se recorta.
"Suspiro", piensa mi punto de vista. "¿Por qué mis padres no me dejan ser más amplio? Hay mucho espacio. Tal vez alguien en Stack Overflow me pueda dar un consejo".
Para hacer un diseño personalizado, debe leer y comprender este artículo https://developer.android.com/guide/topics/ui/how-android-draws.html
No es difícil implementar el comportamiento que quieres. Solo debe anular onMeasure y onLayout en su vista personalizada.
En onMeasure obtendrá la altura máxima posible de su vista personalizada y call medida () para niños en ciclo. Después de la medición del niño, obtenga la altura deseada de cada niño y calcule si el niño encaja en la columna actual o, si no, aumente la vista personalizada para la nueva columna.
En onLayout, debe llamar a layout () para todas las vistas secundarias para establecer las posiciones dentro del padre. Estas posiciones las has calculado en Medida antes.
Actualización: Código modificado para arreglar algunas cosas.
Primero, permítame decir que usted hizo una gran pregunta y explicó muy bien el problema (¡dos veces!) Aquí está mi solución:
Parece que hay mucho que hacer con onMeasure
que, en la superficie, no tiene mucho sentido. Como ese es el caso, dejaremos que onMeasure
ejecute y, al final, onMeasure
juicio sobre los límites de la View
en onLayout
estableciendo mStickyWidth
en el nuevo ancho mínimo que aceptaremos. En onPreDraw
, usando un ViewTreeObserver.OnPreDrawListener
, ViewTreeObserver.OnPreDrawListener
otro diseño ( requestLayout
). De la documentation (énfasis agregado):
boolean onPreDraw ()
Se invoca el método de devolución de llamada cuando el árbol de vista está a punto de dibujarse. En este punto, todas las vistas en el árbol se han medido y se les ha dado un marco. Los clientes pueden usar esto para ajustar sus límites de desplazamiento o incluso para solicitar un nuevo diseño antes de que ocurra el dibujo .
El nuevo ancho mínimo establecido en onLayout
ahora será aplicado por onMeasure
que ahora es más inteligente sobre lo que es posible.
He probado esto con su código de ejemplo y parece funcionar bien. Necesitará muchas más pruebas. Puede haber otras formas de hacer esto, pero esa es la esencia del enfoque.
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;
}