¿Manera autorizada de anular onMeasure ()?

88

¿Cuál es la forma correcta de anular onMeasure ()? He visto varios enfoques. Por ejemplo, Professional Android Development usa MeasureSpec para calcular las dimensiones, luego termina con una llamada a setMeasuredDimension (). Por ejemplo:

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

Por otro lado, según esta publicación , la forma "correcta" es usar MeasureSpec, llamar a setMeasuredDimensions (), seguido de una llamada a setLayoutParams (), y terminar con una llamada a super.onMeasure (). Por ejemplo:

@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);
}

Entonces, ¿cuál es la forma correcta? Ninguno de los enfoques me ha funcionado al 100%.

Supongo que realmente lo que estoy preguntando es si alguien sabe de un tutorial que explique onMeasure (), diseño, dimensiones de vistas secundarias, etc.

U Avalos
fuente
15
¿Encontraste la respuesta útil / correcta? ¿Por qué no marcarlo como la respuesta?
superjos

Respuestas:

68

Las otras soluciones no son completas. Pueden funcionar en algunos casos y son un buen lugar para comenzar, pero es posible que no se garantice que funcionen.

Cuando se llama a onMeasure, es posible que tenga o no los derechos para cambiar el tamaño. Los valores que se pasan a su onMeasure ( widthMeasureSpec, heightMeasureSpec) contienen información sobre lo que su vista secundaria puede hacer. Actualmente hay tres valores:

  1. MeasureSpec.UNSPECIFIED - Puedes ser tan grande como quieras
  2. MeasureSpec.AT_MOST- Tan grande como desee (hasta el tamaño de la especificación), esto es parentWidthen su ejemplo.
  3. MeasureSpec.EXACTLY- Sin elección. Padre ha elegido.

Esto se hace para que Android pueda realizar varias pasadas para encontrar el tamaño correcto para cada elemento, consulte aquí para obtener más detalles.

Si no sigue estas reglas, no se garantiza que su enfoque funcione.

Por ejemplo, si desea comprobar si está autorizado a cambiar el tamaño, puede hacer lo siguiente:

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

Usando esta información sabrá si puede modificar los valores como en su código. O si se le pide que haga algo diferente. Una forma rápida y sencilla de resolver el tamaño deseado es utilizar uno de los siguientes métodos:

int resolveSizeAndState (int tamaño, int medidaSpec, int childMeasuredState)

int resolveSize (int tamaño, int medidaSpec)

Mientras que el primero solo está disponible en Honeycomb, el segundo está disponible en todas las versiones.

Nota: Puede encontrar eso resizeWidtho resizeHeightsiempre es falso. Descubrí que este era el caso si estaba solicitando MATCH_PARENT. Pude solucionar este problema solicitando WRAP_CONTENTen mi diseño principal y luego, durante la fase NO ESPECIFICADA, solicitando un tamaño de Integer.MAX_VALUE. Hacerlo le da el tamaño máximo que su padre permite en el próximo paso a través de onMeasure.

Grimmace
fuente
39

La documentación es la autoridad en este asunto: http://developer.android.com/guide/topics/ui/how-android-draws.html y http://developer.android.com/guide/topics/ui/custom -components.html

Para resumir: al final de su onMeasuremétodo anulado debe llamar setMeasuredDimension.

No debe llamar super.onMeasuredespués de llamar setMeasuredDimension, eso simplemente borrará lo que haya configurado. En algunas situaciones, es posible que desee llamar al super.onMeasureprimero y luego modificar los resultados llamando setMeasuredDimension.

No llame setLayoutParamsen onMeasure. El diseño ocurre en una segunda pasada después de medir.

satur9nine
fuente
Mi experiencia es que si anulo onMeasure sin llamar a super.onMeasure, OnLayout no se llama en las vistas secundarias.
William Jockusch
2

Creo que depende del padre que está anulando.

Por ejemplo, si está ampliando un ViewGroup (como FrameLayout), cuando haya medido el tamaño, debe llamar como se muestra a continuación

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

porque es posible que desee que ViewGroup haga el trabajo de descanso (haga algunas cosas en la vista infantil)

Si está ampliando una Vista (como ImageView), puede simplemente llamar this.setMeasuredDimension(width, height);, porque la clase principal simplemente hará algo como lo ha hecho normalmente.

En una palabra, si desea algunas funciones que su clase principal ofrece gratis, debe llamar super.onMeasure()(pasar MeasureSpec.EXACTLY modo medida especificación generalmente), de lo contrario, llamar this.setMeasuredDimension(width, height);es suficiente.

AQUÉL
fuente
1

así es como resolví el problema:

@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);

}

Además, era necesario que el componente ViewPager

Yuli Reiri
fuente
3
Ésta no es la solución adecuada. super.onMeasureborrará las dimensiones establecidas usando setMeasuredDimension.
tomrozb
@tomrozb no es humptydevelopers.com/2013/05/… y puedes consultar las fuentes de Android
Yuli Reiri
@YuliReiri: ¡Solución perfecta!
Shayan Tabatabaee
0

Si cambia el tamaño de las vistas dentro de onMeasuretodo lo que necesita es la setMeasuredDimensionllamada. Si está cambiando el tamaño fuera de onMeasureusted necesita llamar setLayoutParams. Por ejemplo, cambiar el tamaño de una vista de texto cuando se cambia el texto.

Kyle O.
fuente
Aún puede llamar a setMinWidth () y setMaxWidth () y hacer que el onMeasure estándar lo maneje. Descubrí que es mejor no llamar a setLayoutParams desde dentro de una clase de vista personalizada. Realmente se usa para ViewGroups o Activities para anular el comportamiento de vista de los niños.
Dorrin
-1

Supongo que setLayoutParams y recalcular las medidas es una solución para cambiar el tamaño de las vistas secundarias correctamente, ya que esto generalmente se hace en el onMeasure de la clase derivada.

Sin embargo, esto rara vez funciona correctamente (por el motivo que sea ...), es mejor invocar a measureChildren (al derivar un ViewGroup) o intentar algo similar cuando sea necesario.

M. Schenk
fuente
-5

puede tomar este fragmento de código como ejemplo 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
fuente
6
Este código es fundamentalmente incorrecto. Primero, no tiene en cuenta el modo de diseño (consulte la respuesta de Grimmace). Eso es malo, pero no verá el efecto de eso porque también llama a super.onMeasure () al final, lo que anula los valores que estableció (consulte la respuesta de satur9nine) y anula el propósito de anular onMeasure () en absoluto.
spaaarky21