Manera simple de hacer un diseño dinámico pero cuadrado

84

Estoy usando GridViewpara mostrar un montón de vistas que son esencialmente LinearLayouts. Quiero LinearLayoutsque todos sean cuadrados, pero también quiero que tengan un tamaño dinámico, es decir, hay dos columnas y quiero LinearLayoutsque se estiren dependiendo del tamaño de la pantalla pero que permanezcan cuadrados. ¿Hay alguna forma de hacer esto a través del xmldiseño o tengo que establecer las alturas y los anchos mediante programación?

Catalina
fuente

Respuestas:

113

Una buena solución para los GridViewelementos cuadrados es extender RelativeLayouto LinearLayoutanular onMeasureasí:

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
jdamcd
fuente
2
Esto está usando la especificación de medida en lugar de usar setMeasuredDimension (), por lo que es bueno. Pero no es muy flexible ya que siempre toma el ancho.
Adam
3
Como dijo @DavidMerriman anteriormente, si la vista es más ancha que alta, esto hará que la vista se extienda por debajo del área de visualización, cortando parte de la vista.
vcapra1
5
Esto sería más flexible si pasara el valor de Math.min (widthMeasureSpec, heightMeasureSpec) en ambos argumentos de super.onMeasure ()
Paul Wintz
Esto funcionará fácilmente con actividades, pero no funcionará con un widget de aplicación.
Apollo-Roboto
73

Puede que sea tarde para responder, pero aún así, será útil para los nuevos visitantes.

Con el nuevo ConstraintLayout introducido en Android Studio 2.3, ahora es bastante fácil crear diseños receptivos.

En un ConstraintLayout padre, para hacer que cualquiera de sus hijos vista / diseño sea dinámicamente cuadrado, agregue este atributo

app:layout_constraintDimensionRatio="w,1:1"

w es para especificar restricciones de ancho y la relación 1: 1 asegura un diseño cuadrado.

Himanshu Chauhan
fuente
9
esta es una respuesta de rey
itzhar
2
¡La mejor respuesta si desea mostrar un diseño existente como un cuadrado!
Quentin Klein
9
Asegúrese de establecer "0dp" para el ancho y alto del diseño en el diseño secundario. De lo contrario, no funcionará.
Thilina Kj
También funciona para la vista en sí, quiero decir, si desea que el elemento secundario de un contrantLayout sea cuadrado, puede definir el atributo layout_constraintDimensionRatio directamente en la vista secundaria
Plinio.Santos
1
Muchas gracias por la solución; He hecho si ayuda a una esencia completa de mi código usando su solución: gist.github.com/nadar71/006699e31ef7451813e6c97dacfbc5e2
android_dev71
44

No hay nada en el xml que le permita vincular las propiedades de ancho y alto. Probablemente lo más fácil de hacer es subclasificar LinearLayouty anularonMeasure

@Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    int size = width > height ? height : width;
    setMeasuredDimension(size, size);
}

Lo he usado para crear vistas que siempre son cuadradas antes. Todavía debería funcionar para a LinearLayout.

Más información que ayudará a hacer esto: http://developer.android.com/guide/topics/ui/custom-components.html http://developer.android.com/reference/android/view/View.MeasureSpec.html

David Merriman
fuente
Sí, terminé haciendo algo similar. ¡Gracias!
Catherine
1
Si esto funcionó para usted, ¿podría marcar esto como la respuesta correcta?
David Merriman
1
@Catherine oye Catherine, ¿puedes publicar tu fragmento? Quizás sería útil para otros :-)
Marek Sebera
3
¡No olvide agregar super.onMeasure(widthMeasureSpec, widthMeasureSpec);:!
deKajoo
1
@deKajoo El uso super.onMeasure(widthMeasureSpec, widthMeasureSpec);te da un cuadrado, pero siempre es ancho x ancho. Si las medidas dadas son más anchas que altas, tendrá un problema. Mi solución usa la menor de las dos medidas, de modo que siempre obtienes el cuadrado más grande que quepa dentro del rectángulo dado.
David Merriman
43

Lo he hecho de esta manera:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int size;
    if(widthMode == MeasureSpec.EXACTLY && widthSize > 0){
        size = widthSize;
    }
    else if(heightMode == MeasureSpec.EXACTLY && heightSize > 0){
        size = heightSize;
    }
    else{
        size = widthSize < heightSize ? widthSize : heightSize;
    }

    int finalMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
    super.onMeasure(finalMeasureSpec, finalMeasureSpec);
}

Con esta implementación, su diseño será cuadrado, asumiendo el tamaño más bajo entre ancho y alto. E incluso se puede configurar con valores dinámicos, como usar el peso dentro de un LinearLayout.

Fernando Camargo
fuente
¿El método "onMeasure" se anulará en una clase que extienda GridView?
An-droide
Tt está anulado en la clase que desea que sea cuadrado. Para el caso de la pregunta, son LinearLayouts dentro de GridView.
Fernando Camargo
Este me ayudó a solucionar algunos problemas extraños al usar una vista cuadrada en un StaggeredGridLayout. Mi implementación más ingenua no fue lo suficientemente buena. ¡Gracias!
Peterdk
10

Podemos hacerlo de una manera muy sencilla: solo llame super.onMeasure()dos veces.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int width = getMeasuredWidth();
    int height = getMeasuredHeight();
    int squareLen = Math.min(width, height);

    super.onMeasure(
        MeasureSpec.makeMeasureSpec(squareLen, MeasureSpec.EXACTLY), 
        MeasureSpec.makeMeasureSpec(squareLen, MeasureSpec.EXACTLY));
}

Al llamar super.onMeasure()dos veces, esto es menos eficiente en términos del proceso de dibujo, pero es una forma sencilla de solucionar los problemas de diseño que pueden causar las otras respuestas.

transeúnte
fuente
2
en lugar de llamar a super.onMeasure dos veces, la segunda vez solo necesita llamar a setMeasuredDimension (squareLen, squareLen);
user3802077
Llamar super.onMeasure()dos veces garantiza que el diseño se realice correctamente dado el nuevo tamaño de la vista. Sin esto, necesitamos activar un diseño de una manera diferente o lidiar con diseños incorrectos.
Richard Le Mesurier
4

Es tan simple como:

public class SquareRelativeLayout extends RelativeLayout {

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

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

    public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
    {
        if (widthMeasureSpec < heightMeasureSpec) 
            super.onMeasure(widthMeasureSpec, widthMeasureSpec);
        else
            super.onMeasure(heightMeasureSpec, heightMeasureSpec);
    }
}
Abdul Wasae
fuente
1

Aquí hay una solución que funciona para todos los parámetros de diseño que se pueden configurar para ver o ver el grupo:

    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    int widthDesc = MeasureSpec.getMode(widthMeasureSpec);
    int heightDesc = MeasureSpec.getMode(heightMeasureSpec);
    int size = 0;
    if (widthDesc == MeasureSpec.UNSPECIFIED
            && heightDesc == MeasureSpec.UNSPECIFIED) {
        size = DP(defaultSize); // Use your own default size, in our case
                                // it's 125dp
    } else if ((widthDesc == MeasureSpec.UNSPECIFIED || heightDesc == MeasureSpec.UNSPECIFIED)
            && !(widthDesc == MeasureSpec.UNSPECIFIED && heightDesc == MeasureSpec.UNSPECIFIED)) {
                    //Only one of the dimensions has been specified so we choose the dimension that has a value (in the case of unspecified, the value assigned is 0)
        size = width > height ? width : height;
    } else {
                    //In all other cases both dimensions have been specified so we choose the smaller of the two
        size = width > height ? height : width;
    }
    setMeasuredDimension(size, size);

Salud

Zaid Daghestani
fuente
Esta respuesta tiene algunas características interesantes y es potencialmente más robusta, pero necesita trabajo y debería construir especificaciones de medida que se pasen a super.onMeasure () en lugar de setMeasuredDimension (), ya que permitiría una variedad de superclases.
Adam
1

Mi sugerencia es crear una clase de diseño personalizado que herede de FrameLayout. Anule el método OnMeasure () y coloque el control que desee que sea cuadrado dentro de ese SquareFrameLayout.

Así es como se hace en Xamarin.Android:

public class SquareFrameLayout : FrameLayout
{
    private const string _tag = "SquareFrameLayout";

    public SquareFrameLayout(Android.Content.Context context):base(context) {}
    public SquareFrameLayout(IntPtr javaReference, Android.Runtime.JniHandleOwnership transfer):base(javaReference, transfer) {}
    public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs):base(context, attrs) {}
    public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs, int defStyleAttr):base(context, attrs, defStyleAttr) {}
    public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes):base(context, attrs, defStyleAttr, defStyleRes) {}

    protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        var widthMode = MeasureSpec.GetMode(widthMeasureSpec);
        int widthSize = MeasureSpec.GetSize(widthMeasureSpec);
        var heightMode = MeasureSpec.GetMode(heightMeasureSpec);
        int heightSize = MeasureSpec.GetSize(heightMeasureSpec);

        int width, height;

        switch (widthMode)
        {
            case MeasureSpecMode.Exactly:
                width = widthSize;
                break;
            case MeasureSpecMode.AtMost:
                width = Math.Min(widthSize, heightSize);
                break;
            default:
                width = 100;
                break;
        }

        switch (heightMode)
        {
            case MeasureSpecMode.Exactly:
                height = heightSize;
                break;
            case MeasureSpecMode.AtMost:
                height = Math.Min(widthSize, heightSize);
                break;
            default:
                height = 100;
                break;
        }

        Log.Debug(_tag, $"OnMeasure({widthMeasureSpec}, {heightMeasureSpec}) => Width mode: {widthMode}, Width: {widthSize}/{width}, Height mode: {heightMode}, Height: {heightSize}/{height}");
        var size = Math.Min(width, height);
        var newMeasureSpec = MeasureSpec.MakeMeasureSpec(size, MeasureSpecMode.Exactly);
        base.OnMeasure(newMeasureSpec, newMeasureSpec);
    }
}

Si desea que una Vista (o cualquier otro control) sea cuadrada (y centrada), simplemente agréguela a su diseño de la siguiente manera:

<your.namespace.SquareFrameLayout
    android:id="@+id/squareContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center">
    <View
        android:id="@+id/squareContent"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</your.namespace.SquareFrameLayout>
sdippl
fuente
1

Agregue la siguiente línea en XML:

app:layout_constraintDimensionRatio="1:1"
Anurag Bhalekar
fuente
1
Quería publicar la misma respuesta porque es mejor que app: layout_constraintDimensionRatio = "w, 1: 1", ya que siempre mantiene la proporción si cambia la configuración
Alexey Simchenko
0

Echa un vistazo a SquareLayout , una biblioteca de Android que proporciona una clase contenedora para diferentes diseños, dándolos dimensiones cuadradas sin perder ninguna funcionalidad principal.

Las dimensiones se calculan justo antes de que se renderice el diseño , por lo tanto, no hay que volver a renderizar ni nada por el estilo para ajustar una vez que se obtiene la vista.

Para usar la biblioteca, agregue esto a su build.gradle:

repositories {
    maven {
        url "https://maven.google.com"
    }
}

dependencies {
    compile 'com.github.kaushikthedeveloper:squarelayout:0.0.3'
}

El que necesita es SquareLinearLayout .

NP Kaushik
fuente
No me funciona en un dispositivo real :( Renderizado bien en la vista previa de Android Studio, pero no funciona en el dispositivo
Eugene Voronoy
0

Para cualquiera que quiera una solución con Kotlin, esto es lo que hice FrameLayout.

package your.package.name

import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout

class SquareLayout: FrameLayout {

    constructor(ctx: Context) : super(ctx)
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        if (widthMeasureSpec < heightMeasureSpec)
            super.onMeasure(widthMeasureSpec, widthMeasureSpec)
        else
            super.onMeasure(heightMeasureSpec, heightMeasureSpec)
    }
}
Agua
fuente
0

Prueba este código:

public class SquareRelativeLayout extends RelativeLayout {
    public SquareRelativeLayout(Context context) {
        super(context);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int size;
        if (widthMode == MeasureSpec.EXACTLY && widthSize > 0) {
            size = widthSize;
        } else if (heightMode == MeasureSpec.EXACTLY && heightSize > 0) {
            size = heightSize;
        } else {
            size = widthSize < heightSize ? widthSize : heightSize;
        }

        int finalMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
        super.onMeasure(finalMeasureSpec, finalMeasureSpec);
    }
}
reza_khalafi
fuente