Android: ¿por qué no hay maxHeight para una vista?

184

Las vistas tienen unminHeight pero de alguna manera carecen de maxHeight:

Lo que estoy tratando de lograr es tener algunos elementos (vistas) llenando a ScrollView. Cuando hay 1..3 elementos, quiero mostrarlos directamente. Lo que significa que ScrollViewtiene la altura de 1, 2 o 3 elementos.

Cuando hay 4 o más elementos, quiero ScrollViewque deje de expandirse (por lo tanto, a maxHeight) y comience a proporcionar desplazamiento.

Sin embargo, desafortunadamente no hay forma de establecer un maxHeight. Por lo tanto, es probable que tenga que establecer mi ScrollViewaltura mediante programación WRAP_CONTENTcuando hay 1..3 elementos y establecer la altura 3*sizeOf(View)cuando hay 4 o más elementos.

¿Alguien puede explicar por qué no se maxHeightproporciona, cuando ya existe minHeight?

(Por cierto: algunas vistas, como ImageViewtener un maxHeightimplementado).

znq
fuente
He publicado una solución en otro hilo: stackoverflow.com/questions/18425758/…
JMPergar
2
He creado una solución para esto en chintanrathod.com/…
Chintan Rathod
Google eliminar maxHeight es molesto e inconveniente. Mira todas las cosas que tienes que hacer ahora para obtener el mismo resultado.
Ian Wambai

Respuestas:

86

Ninguna de estas soluciones funcionó para lo que necesitaba, que era un ScrollView configurado para wrap_content pero que tenía un maxHeight para que dejara de expandirse después de cierto punto y comenzara a desplazarse. Simplemente anulé el método onMeasure en ScrollView.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(300, MeasureSpec.AT_MOST);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Es posible que esto no funcione en todas las situaciones, pero ciertamente me da los resultados necesarios para mi diseño. Y también aborda el comentario de madhu.

Si hay algún diseño presente debajo de la vista de desplazamiento, entonces este truco no funcionará - madhu 5 de marzo a las 4:36

silbido
fuente
1
¿Cuándo no funcionaría esto? Esto parece una solución muy limpia. Me hace preguntarme por qué no lo implementaron en el xml.
Christophe Smet
1
Probablemente no funcionaría si configurara manualmente la altura para decir 240. No me molesté en verificar el modo en que se encuentra la vista de desplazamiento porque solo necesitaba admitir 1 modo específico (wrap_content).
whizzle
Hay una edición para esta respuesta que dice 'Si hay algún diseño presente debajo de la vista de desplazamiento, entonces este truco no funcionará', lo cual no es cierto. Tengo un diseño alineado en la parte inferior de la página y este diseño personalizado tiene "android: layout_above = '@ + ...'" que siempre coloca el diseño personalizado sobre el diseño inferior :)
Machine Tribe
70

Para crear un ScrollViewo ListViewcon maxHeight solo necesita crear un diseño lineal transparente alrededor de él con una altura de lo que desea que sea maxHeight. Luego establece la Altura del ScrollView en wrap_content. Esto crea un ScrollView que parece crecer hasta que su altura es igual a la LinearLayout principal.

JustinMorris
fuente
3
Esto fue útil para evitar que un diálogo con contenido de desplazamiento se expanda a la altura total de la pantalla.
Kyle Ivey
Una pequeña sugerencia sería usar un FrameLayout en lugar de un LinearLayout, pero dudo que sea importante.
Kyle Ivey
43
Si hay algún diseño presente debajo de la vista de desplazamiento, entonces este truco no funcionará
Sreedhu Madhu
2
¿puede por favor proporcionar algún código? no funcionó para mí, así que tal vez estoy haciendo algo mal ...
desarrollador de Android
no funcionará si tienes botones debajo. sacará los botones de la pantalla
Li Tian Gong
43

Esto funcionó para mí para hacerlo personalizable en xml:

MaxHeightScrollView.java:

public class MaxHeightScrollView extends ScrollView {

private int maxHeight;
private final int defaultHeight = 200;

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

public MaxHeightScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
    if (!isInEditMode()) {
        init(context, attrs);
    }
}

public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    if (!isInEditMode()) {
        init(context, attrs);
    }
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    if (!isInEditMode()) {
        init(context, attrs);
    }
}

private void init(Context context, AttributeSet attrs) {
    if (attrs != null) {
        TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView);
        //200 is a defualt value
        maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_maxHeight, defaultHeight);

        styledAttrs.recycle();
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

attr.xml

<declare-styleable name="MaxHeightScrollView">
        <attr name="maxHeight" format="dimension" />
    </declare-styleable>

diseño de ejemplo

<blah.blah.MaxHeightScrollView android:layout_weight="1"
                app:maxHeight="90dp"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content">
                <EditText android:id="@+id/commentField"
                    android:hint="Say Something"
                    android:background="#FFFFFF"
                    android:paddingLeft="8dp"
                    android:paddingRight="8dp"
                    android:gravity="center_vertical"
                    android:maxLines="500"
                    android:minHeight="36dp"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content" />
            </blah.blah.MaxHeightScrollView>
SlickDev
fuente
Crear una vista personalizada es definitivamente la mejor manera de lograrlo.
Bertram Gilfoyle
¡Y esto funciona si tiene un diseño debajo de la vista de desplazamiento! ¡Perfecto!
Ragaisis
25

(Sé que esto no responde directamente a la pregunta, pero podría ser útil para otras personas que buscan la funcionalidad maxHeight)

ConstraintLayout ofrece altura máxima para sus hijos a través de

app:layout_constraintHeight_max="300dp"
app:layout_constrainedHeight="true" 

o

app:layout_constraintWidth_max="300dp"
app:layout_constrainedWidth="true" 

Muestra de uso aquí .

usuario1506104
fuente
2
Esto no funciona si la vista está en una cadena vertical (como se especifica en los documentos de
RestraintLayout
Gracias por la info.
user1506104
8

Hubiera comentado la respuesta de whizzle si pudiera, pero me pareció útil tener en cuenta que para poder resolver este problema en el contexto del modo de ventanas múltiples en Android N, necesitaba cambiar el código ligeramente a esto:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if(MeasureSpec.getSize(heightMeasureSpec) > maxHeight) {
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Esto permite que el tamaño del diseño sea más pequeño que la altura máxima, pero también evita que sea más grande que la altura máxima. Utilicé esta es una clase de diseño que anula RelativeLayouty esto me permitió crear un cuadro de diálogo personalizado con un elemento ScrollViewsecundario MaxHeightRelativeLayoutque no expande la altura completa de la pantalla y también se reduce para ajustarse al tamaño de viuda más pequeño en múltiples ventanas para Android NORTE.

JP Krause
fuente
4

No hay forma de establecer maxHeight. Pero puedes establecer la Altura.

Para hacerlo, deberá descubrir la altura de cada elemento de su scrollView. Después de eso, simplemente configure la altura de scrollView en numberOfItens * heightOfItem.

Para descubrir la altura de un artículo haz eso:

View item = adapter.getView(0, null, scrollView);
item.measure(0, 0);
int heightOfItem = item.getMeasuredHeight();

Para establecer la altura, haga eso:

// if the scrollView already has a layoutParams:
scrollView.getLayoutParams().height = heightOfItem * numberOfItens;
// or
// if the layoutParams is null, then create a new one.
scrollView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, heightOfItem * numberOfItens));
Derzu
fuente
4

Envuelva su ScrollViewalrededor de un plano LinearLayoutcon layout_height = "max_height", esto hará un trabajo perfecto. De hecho, tengo este código en producción de los últimos 5 años con cero problemas.

<LinearLayout
        android:id="@+id/subsParent"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:gravity="bottom|center_horizontal"
        android:orientation="vertical">

        <ScrollView
            android:id="@+id/subsScroll"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_marginEnd="15dp"
            android:layout_marginStart="15dp">

            <TextView
                android:id="@+id/subsTv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/longText"
                android:visibility="visible" />

        </ScrollView>
    </LinearLayout>
Sai
fuente
1
Solución perfecta de hecho. Inteligente y simple. Es adecuado incluso para la vista de reciclaje.
Alex Belets
3

Mi MaxHeightScrollViewvista personalizada

public class MaxHeightScrollView extends ScrollView {
    private int maxHeight;

    public MaxHeightScrollView(Context context) {
        this(context, null);
    }

    public MaxHeightScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    private void init(Context context, AttributeSet attrs) {
        TypedArray styledAttrs =
                context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView);
        try {
            maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_mhs_maxHeight, 0);
        } finally {
            styledAttrs.recycle();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (maxHeight > 0) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

style.xml

<declare-styleable name="MaxHeightScrollView">
    <attr name="mhs_maxHeight" format="dimension" />
</declare-styleable>

Utilizando

<....MaxHeightScrollView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:mhs_maxHeight="100dp"
    >

    ...

</....MaxHeightScrollView>
Phan Van Linh
fuente
2

Tengo una respuesta aquí:

https://stackoverflow.com/a/29178364/1148784

Simplemente cree una nueva clase que extienda ScrollView y anule su onMeasuremétodo.

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (maxHeight > 0){
            int hSize = MeasureSpec.getSize(heightMeasureSpec);
            int hMode = MeasureSpec.getMode(heightMeasureSpec);

            switch (hMode){
                case MeasureSpec.AT_MOST:
                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(hSize, maxHeight), MeasureSpec.AT_MOST);
                    break;
                case MeasureSpec.UNSPECIFIED:
                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
                    break;
                case MeasureSpec.EXACTLY:
                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(hSize, maxHeight), MeasureSpec.EXACTLY);
                    break;
            }
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
babay
fuente
2

Como se mencionó anteriormente, ConstraintLayout ofrece la altura máxima para sus hijos a través de:

app:layout_constraintHeight_max="300dp"
app:layout_constrainedHeight="true"

Además, si la altura máxima para un niño de ConstraintLayout es incierta hasta que se ejecute la aplicación, todavía hay una manera de hacer que este niño adapte automáticamente una altura mutable sin importar dónde se colocó en la cadena vertical.

Por ejemplo, necesitamos mostrar un diálogo inferior con un encabezado mutable TextView, un ScrollView mutable y un pie de página mutable TextView. La altura máxima del diálogo es 320dp, cuando la altura total no alcanza 320dp ScrollView actúa como wrap_content, cuando la altura total excede ScrollView actúa como "maxHeight = 320dp - altura del encabezado - altura del pie de página".

Podemos lograr esto solo a través del archivo de diseño xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="320dp">

    <TextView
        android:id="@+id/tv_header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/black_10"
        android:gravity="center"
        android:padding="10dp"
        app:layout_constraintBottom_toTopOf="@id/scroll_view"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="1"
        app:layout_constraintVertical_chainStyle="packed"
        tools:text="header" />

    <ScrollView
        android:id="@+id/scroll_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/black_30"
        app:layout_constrainedHeight="true"
        app:layout_constraintBottom_toTopOf="@id/tv_footer"
        app:layout_constraintHeight_max="300dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_header">

        <LinearLayout
            android:id="@+id/ll_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tv_sub1"
                android:layout_width="match_parent"
                android:layout_height="160dp"
                android:gravity="center"
                android:textColor="@color/orange_light"
                tools:text="sub1" />

            <TextView
                android:id="@+id/tv_sub2"
                android:layout_width="match_parent"
                android:layout_height="160dp"
                android:gravity="center"
                android:textColor="@color/orange_light"
                tools:text="sub2" />

        </LinearLayout>
    </ScrollView>

    <TextView
        android:id="@+id/tv_footer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/black_50"
        android:gravity="center"
        android:padding="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/scroll_view"
        tools:text="footer" />
</android.support.constraint.ConstraintLayout>

La mayoría del código de importación es corto:

app:layout_constraintVertical_bias="1"
app:layout_constraintVertical_chainStyle="packed"

app:layout_constrainedHeight="true"

El uso de maxWidth horizontal es bastante similar.

xionghg
fuente
1

¿Has intentado usar el valor de layout_weight ? Si establece uno en un valor mayor que 0, extenderá esa vista al espacio restante disponible.

Si tenía varias vistas que debían ampliarse, el valor se convertirá en un peso entre ellas.

Entonces, si tuviera dos vistas establecidas en un valor de layout_weight de 1, ambas se estirarían para llenar el espacio, pero ambas se estirarían en la misma cantidad de espacio. Si establece uno de ellos en el valor de 2, entonces se estiraría el doble que la otra vista.

Aquí encontrará más información en Diseño lineal.

Bryan Denny
fuente
1
El problema con los pesos es que son relativos. Diferentes tamaños de pantalla, diferentes resultados, por ejemplo, con una pantalla de 800 píxeles, puedo establecer un peso en un cierto valor, de modo que un área específica termine con los 200 píxeles deseados. Sin embargo, con una pantalla de 850px, el área específica termina siendo más grande que 200px, pero los elementos encerrados siguen siendo solo 200px. Es por eso que preferí tener uno maxHeightque pueda establecer en un valor de inmersión específico. (Finalmente terminé para calcular y establecer las alturas mediante programación)
znq
1

Creo que puede configurar la altura en tiempo de ejecución para 1 elemento solo scrollView.setHeight(200px), para 2 elementos scrollView.setheight(400px)para 3 o másscrollView.setHeight(600px)

Yayo28
fuente
8
No desea establecer la dimensión en 'px'. Es muy probable que no obtenga el resultado que desea en varios dispositivos. developer.android.com/guide/topics/resources/…
Hernd DerBeld
@HerndDerBeld así es como funciona en código. si lo desea, siempre puede convertir de DP a píxeles con bastante facilidad: píxeles flotantes = TypedValue.applyDimension (TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources () .getDisplayMetrics ());
Desarrollador de Android
1

Como sabemos, los dispositivos con Android pueden tener diferentes tamaños de pantalla. Como sabemos, las vistas deben ajustarse dinámicamente y convertirse en el espacio apropiado.

Si establece una altura máxima, puede forzar la vista a no tener suficiente espacio o ocupar menos espacio. Sé que a veces parece ser prácticamente establecer una altura máxima. Pero si la resolución cambia drásticamente, ¡y lo hará!, Entonces la vista, que tiene una altura máxima, no se verá adecuada.

Creo que no hay una forma adecuada de hacer exactamente el diseño que desea. Le recomendaría que reflexione sobre su diseño utilizando administradores de diseño y mecanismos relativos. No sé qué estás tratando de lograr, pero me parece un poco extraño que una lista solo muestre tres elementos y luego el usuario tenga que desplazarse.

por cierto. minHeight no está garantizado (y quizás tampoco debería existir). puede tener algún beneficio forzar que los elementos sean visibles mientras que otros elementos relativos se hacen más pequeños.

Diego Frehner
fuente
66
Esto está mal. Según esta lógica, tampoco podría decir android: layout_width = "500dp".
Glenn Maynard
No veo qué debería estar mal ni tu interpretación lógica. Si configura layout_width (que a veces está bien), no se garantiza que lo obtenga. 500dp puede verse en un dispositivo apropiado, en otro no. Gerentes de diseño ftw! Tal vez podría explicar más bcs el voto negativo molesta :)
Diego Frehner
3
Es tan válido decir android: maxWidth = "500dp" como decir android: layout_width = "500dp". Ambos fuerzan dimensiones específicas en la vista.
Glenn Maynard
1

Si alguien está considerando usar el valor exacto para LayoutParams, por ejemplo

setLayoutParams(new LayoutParams(Y, X );

Recuerde tener en cuenta la densidad de la pantalla del dispositivo; de lo contrario, podría obtener un comportamiento muy extraño en diferentes dispositivos. P.ej:

Display display = getWindowManager().getDefaultDisplay();
DisplayMetrics d = new DisplayMetrics();
display.getMetrics(d);
setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, (int)(50*d.density) ));
Nicklas Gnejs Eriksson
fuente
0

Primero obtenga la altura del elemento en píxeles

View rowItem = adapter.getView(0, null, scrollView);   
rowItem.measure(0, 0);    
int heightOfItem = rowItem.getMeasuredHeight();

entonces simplemente

Display display = getWindowManager().getDefaultDisplay();    
DisplayMetrics displayMetrics = new DisplayMetrics();    
display.getMetrics(displayMetrics);    
scrollView.getLayoutParams().height = (int)((heightOfItem * 3)*displayMetrics .density);
Ibrahim Khaled
fuente
0

si quieren hacer una vista de desplazamiento o lista de desbordamiento, pero solo en un RelativeLayout con una vista superior y una vista inferior en la parte superior e inferior para ello:

<ScrollView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_above="@+id/topview"
    android:layout_below="@+id/bottomview" >
Un Quoc Duy Nguyen
fuente
Esto necesita más crédito. Implementación específica pero simple.
Luke Allison
0

Usé un ScrollView personalizado hecho en Kotlin que usa maxHeight. Ejemplo de uso:

<com.antena3.atresplayer.tv.ui.widget.ScrollViewWithMaxHeight
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:maxHeight="100dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
</com.antena3.atresplayer.tv.ui.widget.ScrollViewWithMaxHeight>

Aquí está el código de ScrollViewWidthMaxHeight:

import android.content.Context
import android.util.AttributeSet
import android.widget.ScrollView
import timber.log.Timber

class ScrollViewWithMaxHeight @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : ScrollView(context, attrs, defStyleAttr) {

companion object {
    var WITHOUT_MAX_HEIGHT_VALUE = -1
}

private var maxHeight = WITHOUT_MAX_HEIGHT_VALUE

init {
    val a = context.obtainStyledAttributes(
        attrs, R.styleable.ScrollViewWithMaxHeight,
        defStyleAttr, 0
    )
    try {
        maxHeight = a.getDimension(
            R.styleable.ScrollViewWithMaxHeight_android_maxHeight,
            WITHOUT_MAX_HEIGHT_VALUE.toFloat()
        ).toInt()
    } finally {
        a.recycle()
    }
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    var heightMeasure = heightMeasureSpec
    try {
        var heightSize = MeasureSpec.getSize(heightMeasureSpec)
        if (maxHeight != WITHOUT_MAX_HEIGHT_VALUE) {
            heightSize = maxHeight
            heightMeasure = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.AT_MOST)
        } else {
            heightMeasure = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.UNSPECIFIED)
        }
        layoutParams.height = heightSize
    } catch (e: Exception) {
        Timber.e(e, "Error forcing height")
    } finally {
        super.onMeasure(widthMeasureSpec, heightMeasure)
    }
}

fun setMaxHeight(maxHeight: Int) {
    this.maxHeight = maxHeight
}
}

que necesita también esta declaración en values/attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ScrollViewWithMaxHeight">
        <attr name="android:maxHeight" />
    </declare-styleable>
</resources>
Mario Velasco
fuente