¿Cómo puedo poner un ListView en un ScrollView sin colapsar?


351

He buscado soluciones para este problema, y ​​la única respuesta que puedo encontrar parece ser " no coloque un ListView en un ScrollView ". Sin embargo, aún no he visto ninguna explicación real de por qué . La única razón por la que puedo encontrar es que Google no cree que debas hacer eso. Bueno, yo sí, así que lo hice.

Entonces la pregunta es, ¿cómo puede colocar un ListView en un ScrollView sin colapsar a su altura mínima?


11
Porque cuando un dispositivo usa una pantalla táctil, no hay una buena manera de diferenciar entre dos contenedores desplazables anidados. Funciona en escritorios tradicionales donde utiliza la barra de desplazamiento para desplazar un contenedor.
Romain Guy

57
Claro que lo hay. Si se tocan dentro del interior, desplácese. Si el interior es demasiado grande, es un mal diseño de la interfaz de usuario. Eso no significa que sea una mala idea en general.
DougW

55
Me topé con esto para mi aplicación de tableta. Si bien no parece ser peor en el teléfono inteligente, esto es horrible en una tableta donde fácilmente podría decidir si el usuario desea desplazarse por el ScrollView externo o interno. @DougW: diseño horrible, estoy de acuerdo.
medusa

44
@Romain: esta es una publicación bastante antigua, por lo que me pregunto si las inquietudes aquí ya se han abordado o si todavía no hay una buena manera de usar un ListView dentro de un ScrollView (o una alternativa que no implique la codificación manual todas las bondades de un ListView en un LinearLayout)?
Jim

3
@ Jim: "o una alternativa que no implique la codificación manual de todas las bondades de un ListView en un LinearLayout" - ponga todo dentro del ListView. ListViewpuede tener múltiples estilos de fila, además de filas no seleccionables.
CommonsWare

Respuestas:


196

Usar a ListViewpara que no se desplace es extremadamente costoso y va en contra de todo ListView. NO deberías hacer esto. Solo usa un LinearLayouten su lugar.


69
La fuente sería yo ya que he estado a cargo de ListView durante los últimos 2 o 3 años :) ListView hace mucho trabajo adicional para optimizar el uso de adaptadores. Intentar solucionarlo todavía hará que ListView haga mucho trabajo que un LinearLayout no tendría que hacer. No entraré en detalles porque no hay suficiente espacio aquí para explicar la implementación de ListView. Esto tampoco resolverá la forma en que ListView se comporta con respecto a los eventos táctiles. Tampoco hay garantía de que si su truco funciona hoy, seguirá funcionando en una versión futura. Usar un LinearLayout no sería mucho trabajo realmente.
Romain Guy

77
Bueno, esa es una respuesta razonable. Si pudiera compartir algunos comentarios, tengo una experiencia de iPhone mucho más significativa, y puedo decirle que la documentación de Apple está mucho mejor escrita con respecto a las características de rendimiento y casos de uso (o antipatrones) como este. En general, la documentación de Android está mucho más distribuida y menos enfocada. Entiendo que hay algunas razones detrás de eso, pero esa es una discusión larga, así que si te sientes obligado a hablar sobre eso, házmelo saber.
DougW

66
Podría escribir fácilmente un método estático que cree un diseño lineal a partir de un adaptador.
Romain Guy

125
Esta NO es una respuesta para How can I put a ListView into a ScrollView without it collapsing?... Puedes decir que no debes hacerlo, pero también dar una respuesta sobre cómo hacerlo, es una buena respuesta. Usted sabe que un ListView tiene algunas otras características interesantes además del desplazamiento, características que LinearLayout no tiene.
Jorge Fuentes González

44
¿Qué sucede si tiene una región que contiene un ListView + otras Vistas y desea que todo se desplace como uno? Parece un caso de uso razonable para colocar un ListView dentro de un ScrollView. Reemplazar el ListView con un LinearLayout no es una solución porque no puede usar adaptadores.
Barry Fruitman el

262

Aquí está mi solución. Soy bastante nuevo en la plataforma Android, y estoy seguro de que esto es un poco complicado, especialmente en la parte de llamar a .measure directamente y configurar la LayoutParams.heightpropiedad directamente, pero funciona.

Todo lo que tiene que hacer es llamar Utility.setListViewHeightBasedOnChildren(yourListView)y se redimensionará para acomodar exactamente la altura de sus elementos.

public class Utility {
    public static void setListViewHeightBasedOnChildren(ListView listView) {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) {
            // pre-condition
            return;
        }

        int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();

        for (int i = 0; i < listAdapter.getCount(); i++) {
            View listItem = listAdapter.getView(i, null, listView);
            if (listItem instanceof ViewGroup) {
                listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
             }

             listItem.measure(0, 0);
             totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
    }
}

66
Acabas de recrear un LinearLayout muy caro :)
Romain Guy

82
Excepto LinearLayoutque a no tiene todas las sutilezas inherentes de a ListView: no tiene divisores, vistas de encabezado / pie de página, un selector de lista que coincida con los colores del tema de la interfaz de usuario del teléfono, etc. Honestamente, no hay razón por la que no pueda desplazarse el contenedor interno hasta que llegue al final y luego deje de interceptar eventos táctiles para que el contenedor externo se desplace. Todos los navegadores de escritorio hacen esto cuando usas la rueda de desplazamiento. El propio Android puede manejar el desplazamiento anidado si está navegando a través del trackball, entonces ¿por qué no a través del tacto?
Neil Traft el

29
listItem.measure(0,0)arrojará un NPE si listItem es una instancia de ViewGroup. listItem.measureif (listItem instanceof ViewGroup) listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
Agregué lo

44
Arreglo para ListViews con relleno diferente a 0:int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
Paul

8
Lamentablemente, este método es un poco defectuoso cuando ListViewpuede contener elementos de altura variable. La llamada a getMeasuredHeight()solo devuelve la altura mínima objetivo en lugar de la altura real. Intenté usar en su getHeight()lugar, pero esto devuelve 0. ¿Alguien tiene alguna idea sobre cómo abordar esto?
Matt Huggins

89

Esto definitivamente funcionará ............
Debe reemplazar su <ScrollView ></ScrollView>archivo XML de diseño con este Custom ScrollViewcomo <com.tmd.utils.VerticalScrollview > </com.tmd.utils.VerticalScrollview >

package com.tmd.utils;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ScrollView;

public class VerticalScrollview extends ScrollView{

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

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

        public VerticalScrollview(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                    Log.i("VerticalScrollview", "onInterceptTouchEvent: DOWN super false" );
                    super.onTouchEvent(ev);
                    break;

            case MotionEvent.ACTION_MOVE:
                    return false; // redirect MotionEvents to ourself

            case MotionEvent.ACTION_CANCEL:
                    Log.i("VerticalScrollview", "onInterceptTouchEvent: CANCEL super false" );
                    super.onTouchEvent(ev);
                    break;

            case MotionEvent.ACTION_UP:
                    Log.i("VerticalScrollview", "onInterceptTouchEvent: UP super false" );
                    return false;

            default: Log.i("VerticalScrollview", "onInterceptTouchEvent: " + action ); break;
        }

        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        super.onTouchEvent(ev);
        Log.i("VerticalScrollview", "onTouchEvent. action: " + ev.getAction() );
         return true;
    }
}

44
Esto es muy bueno. También hace posible poner un Fragmento de Google Maps dentro del ScrollView, y aún funciona al acercar / alejar. Gracias.
Magnus Johansson

¿Alguien ha notado alguna desventaja con este enfoque? Es muy simple, me temo: o
Marija Milosevic

1
Buena solución, debería ser la respuesta aceptada +1! Lo he mezclado con la respuesta a continuación (de djunod), ¡y eso funciona muy bien!
tanou

1
Esta es la única solución que hace que el desplazamiento Y HAGA CLIC en los niños que trabajan al mismo tiempo para mí.
Muchas

25

En lugar de poner ListViewdentro de a ScrollView, podemos usar ListViewcomo a ScrollView. Las cosas que tienen que estar adentro ListViewse pueden poner dentro del ListView. Se ListViewpueden colocar otros diseños en la parte superior e inferior agregando diseños al encabezado y pie de página ListView. Así que todo ListViewte dará una experiencia de desplazamiento.


3
Esta es la respuesta más elegante y apropiada en mi humilde opinión. Para obtener más detalles sobre este enfoque, consulte esta respuesta: stackoverflow.com/a/20475821/1221537
adamdport

21

Hay muchas situaciones en las que tiene mucho sentido tener ListView en un ScrollView.

Aquí está el código basado en la sugerencia de DougW ... funciona en un fragmento, requiere menos memoria.

public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null) {
        return;
    }
    int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
    int totalHeight = 0;
    View view = null;
    for (int i = 0; i < listAdapter.getCount(); i++) {
        view = listAdapter.getView(i, view, listView);
        if (i == 0) {
            view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, LayoutParams.WRAP_CONTENT));
        }
        view.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
        totalHeight += view.getMeasuredHeight();
    }
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}

llame a setListViewHeightBasedOnChildren (vista de lista) en cada vista de lista incrustada.


No funcionará cuando los elementos de la lista sean de tamaños variables, es decir, algunas vistas de texto que se expanden en varias líneas. ¿Alguna solución?
Khobaib

Mira mi comentario aquí: da la solución perfecta - stackoverflow.com/a/23315696/1433187
Khobaib

funcionó para mí con API 19, pero no con API 23: aquí agrega mucho espacio en blanco debajo de listView.
Lucker10

17

ListView ya es capaz de medirse para ser lo suficientemente alto como para mostrar todos los elementos, pero no hace esto cuando simplemente especifica wrap_content (MeasureSpec.UNSPECIFIED). Lo hará cuando tenga una altura con MeasureSpec.AT_MOST. Con este conocimiento, puede crear una subclase muy simple para resolver este problema que funciona mucho mejor que cualquiera de las soluciones publicadas anteriormente. Aún debe usar wrap_content con esta subclase.

public class ListViewForEmbeddingInScrollView extends ListView {
    public ListViewForEmbeddingInScrollView(Context context) {
        super(context);
    }

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 4, MeasureSpec.AT_MOST));
    }
}

La manipulación de heightMeasureSpec para que sea AT_MOST con un tamaño muy grande (Integer.MAX_VALUE >> 4) hace que ListView mida a todos sus hijos hasta la altura dada (muy grande) y establezca su altura en consecuencia.

Esto funciona mejor que las otras soluciones por algunas razones:

  1. Mide todo correctamente (relleno, divisores)
  2. Mide el ListView durante el paso de medida
  3. Debido al n. ° 2, maneja los cambios en el ancho o el número de elementos correctamente sin ningún código adicional

En el lado negativo, podría argumentar que hacer esto depende de un comportamiento indocumentado en el SDK que podría cambiar. Por otro lado, podría argumentar que así es como realmente debería funcionar wrap_content con ListView y que el comportamiento actual de wrap_content simplemente está roto.

Si le preocupa que el comportamiento pueda cambiar en el futuro, simplemente copie la función onMeasure y las funciones relacionadas de ListView.java y en su propia subclase, luego haga que la ruta AT_MOST a través de onMeasure se ejecute también para UNSPECIFIED.

Por cierto, creo que este es un enfoque perfectamente válido cuando trabajas con un pequeño número de elementos de la lista. Puede ser ineficiente en comparación con LinearLayout, pero cuando el número de elementos es pequeño, usar LinearLayout es una optimización innecesaria y, por lo tanto, una complejidad innecesaria.


¡También funciona con elementos de altura variable en la vista de lista!
Denny

@Jason Y, ¿qué significa esto? Integer.MAX_VALUE >> 4 significa ?? ¿Qué hay 4 allí?
KJEjava48

@ Jason Y para mí funcionó solo después de que cambié Integer.MAX_VALUE >> 2 a Integer.MAX_VALUE >> 21. ¿Por qué es así? También funciona con ScrollView, no con NestedScrollView.
KJEjava48

13

Hay una configuración incorporada para ello. En el ScrollView:

android:fillViewport="true"

En Java

mScrollView.setFillViewport(true);

Romain Guy lo explica en profundidad aquí: http://www.curious-creature.org/2010/08/15/scrollviews-handy-trick/


2
Eso funciona para un LinearLayout como lo demuestra. No funciona para un ListView. Según la fecha de esa publicación, imagino que lo escribió para proporcionar un ejemplo de su alternativa, pero esto no responde a la pregunta.
DougW

esta es una solución rápida

10

Usted crea ListView personalizado que no es desplazable

public class NonScrollListView extends ListView {

    public NonScrollListView(Context context) {
        super(context);
    }
    public NonScrollListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public NonScrollListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
                    Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
            ViewGroup.LayoutParams params = getLayoutParams();
            params.height = getMeasuredHeight();    
    }
}

En su archivo de recursos de diseño

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

    <!-- com.Example Changed with your Package name -->

    <com.Example.NonScrollListView
        android:id="@+id/lv_nonscroll_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
    </com.Example.NonScrollListView>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/lv_nonscroll_list" >

        <!-- Your another layout in scroll view -->

    </RelativeLayout>
</RelativeLayout>

En archivo Java

Cree un objeto de su CustomListview en lugar de ListView como: NonScrollListView non_scroll_list = (NonScrollListView) findViewById (R.id.lv_nonscroll_list);


8

No podríamos usar dos desplazamientos simultáneos. Obtendremos la longitud total de ListView y expandiremos la vista de lista con la altura total. Luego, podemos agregar ListView en ScrollView directamente o usando LinearLayout porque ScrollView tiene directamente un hijo. copie el método setListViewHeightBasedOnChildren (lv) en su código y expanda la vista de lista, luego puede usar la vista de lista dentro de la vista de desplazamiento. \ layout xml file

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
 <ScrollView

        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
         android:background="#1D1D1D"
        android:orientation="vertical"
        android:scrollbars="none" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="#1D1D1D"
            android:orientation="vertical" >

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="40dip"
                android:background="#333"
                android:gravity="center_vertical"
                android:paddingLeft="8dip"
                android:text="First ListView"
                android:textColor="#C7C7C7"
                android:textSize="20sp" />

            <ListView
                android:id="@+id/first_listview"
                android:layout_width="260dp"
                android:layout_height="wrap_content"
                android:divider="#00000000"
               android:listSelector="#ff0000"
                android:scrollbars="none" />

               <TextView
                android:layout_width="fill_parent"
                android:layout_height="40dip"
                android:background="#333"
                android:gravity="center_vertical"
                android:paddingLeft="8dip"
                android:text="Second ListView"
                android:textColor="#C7C7C7"
                android:textSize="20sp" />

            <ListView
                android:id="@+id/secondList"
                android:layout_width="260dp"
                android:layout_height="wrap_content"
                android:divider="#00000000"
                android:listSelector="#ffcc00"
                android:scrollbars="none" />
  </LinearLayout>
  </ScrollView>

   </LinearLayout>

Método onCreate en la clase de actividad:

 import java.util.ArrayList;
  import android.app.Activity;
 import android.os.Bundle;
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
 import android.widget.ListAdapter;
  import android.widget.ListView;

   public class MainActivity extends Activity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.listview_inside_scrollview);
    ListView list_first=(ListView) findViewById(R.id.first_listview);
    ListView list_second=(ListView) findViewById(R.id.secondList);
    ArrayList<String> list=new ArrayList<String>();
    for(int x=0;x<30;x++)
    {
        list.add("Item "+x);
    }

       ArrayAdapter<String> adapter=new ArrayAdapter<String>(getApplicationContext(), 
          android.R.layout.simple_list_item_1,list);               
      list_first.setAdapter(adapter);

     setListViewHeightBasedOnChildren(list_first);

      list_second.setAdapter(adapter);

    setListViewHeightBasedOnChildren(list_second);
   }



   public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null) {
        // pre-condition
        return;
    }

    int totalHeight = 0;
    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);
        listItem.measure(0, 0);
        totalHeight += listItem.getMeasuredHeight();
    }

    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight
            + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
      }

Buena solución! Funciona según sea necesario. Gracias. En mi caso, tengo muchas vistas antes de la lista y la solución perfecta es tener solo un desplazamiento vertical en la vista.
Proverbio

4

Esta es una combinación de las respuestas de DougW, Good Guy Greg y Paul. Descubrí que todo era necesario al intentar usar esto con un adaptador de vista de lista personalizado y elementos de lista no estándar; de lo contrario, la vista de lista bloqueó la aplicación (también se bloqueó con la respuesta de Nex):

public void setListViewHeightBasedOnChildren(ListView listView) {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) {
            return;
        }

        int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
        for (int i = 0; i < listAdapter.getCount(); i++) {
            View listItem = listAdapter.getView(i, null, listView);
            if (listItem instanceof ViewGroup)
                listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
            listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
    }

¿Dónde llamar a este método?
Dhrupal

Después de crear la vista de lista y configurar el adaptador.
Carro abandonado el

Esto se vuelve demasiado lento en listas largas.
cakan

@cakan También se escribió 2 años antes como una solución alternativa para permitir el uso conjunto de ciertos componentes de la interfaz de usuario que realmente no están destinados a combinarse. Está agregando una cantidad innecesaria de sobrecarga en el Android moderno, por lo que es de esperar que la lista se desacelere cuando se haga grande.
Carrito abandonado el

3

No debe poner un ListView en un ScrollView porque un ListView ya es un ScrollView. Eso sería como poner un ScrollView en un ScrollView.

¿Qué está tratando de lograr?


44
Estoy tratando de lograr tener un ListView dentro de un ScrollView. No quiero que mi ListView sea desplazable, pero no existe una propiedad simple para configurar myListView.scrollEnabled = false;
DougW

Como dijo Romain Guy, este no es el propósito de ListView. Un ListView es una vista que está optimizada para mostrar una larga lista de elementos, con mucha lógica complicada para realizar una carga lenta de vistas, reutilizar vistas, etc. Nada de esto tiene sentido si le dice a ListView que no se desplace. Si solo desea un montón de elementos en una columna vertical, use un diseño lineal.
Cheryl Simon el

99
El problema es que hay una serie de soluciones que proporciona ListView, incluida la que usted menciona. Sin embargo, gran parte de la funcionalidad que los Adaptadores simplifican tiene que ver con la gestión y el control de datos, no con la optimización de la interfaz de usuario. En mi opinión, debería haber un simple ListView y uno más complejo que hace que todos los elementos de la lista se reutilicen y esas cosas.
DougW

2
Absolutamente de acuerdo. Tenga en cuenta que es fácil usar un adaptador existente con un diseño lineal, pero quiero todas las cosas buenas que proporciona ListView, como divisores, y que no tengo ganas de implementarlo manualmente.
Artem Russakovskii

1
@ArtemRussakovskii ¿Puede explicar su manera fácil de reemplazar ListView a LinearLayout con un adaptador existente?
happyhardik

3

Convertí @ DougW's Utilityen C # (usado en Xamarin). Lo siguiente funciona bien para los elementos de altura fija en la lista, y será mayormente bueno, o al menos un buen comienzo, si solo algunos de los elementos son un poco más grandes que el elemento estándar.

// You will need to put this Utility class into a code file including various
// libraries, I found that I needed at least System, Linq, Android.Views and 
// Android.Widget.
using System;
using System.Linq;
using Android.Views;
using Android.Widget;

namespace UtilityNamespace  // whatever you like, obviously!
{
    public class Utility
    {
        public static void setListViewHeightBasedOnChildren (ListView listView)
        {
            if (listView.Adapter == null) {
                // pre-condition
                return;
            }

            int totalHeight = listView.PaddingTop + listView.PaddingBottom;
            for (int i = 0; i < listView.Count; i++) {
                View listItem = listView.Adapter.GetView (i, null, listView);
                if (listItem.GetType () == typeof(ViewGroup)) {
                    listItem.LayoutParameters = new LinearLayout.LayoutParams (ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
                }
                listItem.Measure (0, 0);
                totalHeight += listItem.MeasuredHeight;
            }

            listView.LayoutParameters.Height = totalHeight + (listView.DividerHeight * (listView.Count - 1));
        }
    }
}

Gracias @DougW, esto me sacó de apuros cuando tuve que trabajar con OtherPeople'sCode. :-)


Una pregunta: ¿Cuándo llamaste al setListViewHeightBasedOnChildren()método? Porque no funciona todo el tiempo para mí.
Alexandre D.

@AlexandreD llama a Utility.setListViewHeightBasedOnChildren (yourListView) cuando ha creado la lista de elementos. es decir, ¡asegúrese de haber creado su lista primero! Descubrí que estaba haciendo una lista, e incluso estaba mostrando mis elementos en Console.WriteLine ... pero de alguna manera los elementos estaban en el alcance incorrecto. Una vez que lo descubrí y me aseguré de tener el alcance adecuado para mi lista, funcionó a las mil maravillas.
Phil Ryan

El hecho es que mis listas se crean en mi ViewModel. ¿Cómo puedo esperar que mis listas se creen en mi vista antes de cualquier llamada a Utility.setListViewHeightBasedOnChildren (yourListView)?
Alexandre D.

3

Esto es lo único que funcionó para mí:

en Lollipop en adelante puedes usar

yourtListView.setNestedScrollingEnabled(true);

Esto habilita o deshabilita el desplazamiento anidado para esta vista si necesita compatibilidad con versiones anteriores del sistema operativo, tendrá que usar RecyclerView.


Esto no tuvo ningún efecto en mis vistas de lista en API 22 en un DialogFragment en una actividad de AppCompat. Todavía colapsan. La respuesta de @Phil Ryan funcionó con un poco de modificación.
Hakan Çelik MK1

3

Antes no era posible. Pero con el lanzamiento de las nuevas bibliotecas de Appcompat y las bibliotecas de diseño, esto se puede lograr.

Solo tienes que usar NestedScrollView https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html

No sé si funcionará con Listview o no, pero funciona con RecyclerView.

Fragmento de código:

<android.support.v4.widget.NestedScrollView 
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</android.support.v4.widget.NestedScrollView>

Genial, no lo he probado, así que no puedo confirmar que un ListView interno no se colapse, pero parece que esa podría ser la intención. Profundamente triste por no ver a Romain Guy en el registro de culpables;)
DougW

1
Esto funciona con recyclerView pero no listView. Gracias
IgniteCoders

2

hey tuve un problema similar. Quería mostrar una vista de lista que no se desplazara y descubrí que manipular los parámetros funcionaba, pero era ineficiente y se comportaría de manera diferente en diferentes dispositivos ... como resultado, esta es una parte de mi código de programación que realmente lo hace de manera muy eficiente .

db = new dbhelper(this);

 cursor = db.dbCursor();
int count = cursor.getCount();
if (count > 0)
{    
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.layoutId);
startManagingCursor(YOUR_CURSOR);

YOUR_ADAPTER(**or SimpleCursorAdapter **) adapter = new YOUR_ADAPTER(this,
    R.layout.itemLayout, cursor, arrayOrWhatever, R.id.textViewId,
    this.getApplication());

int i;
for (i = 0; i < count; i++){
  View listItem = adapter.getView(i,null,null);
  linearLayout.addView(listItem);
   }
}

Nota: si usa esto, notifyDataSetChanged();no funcionará según lo previsto, ya que las vistas no se volverán a dibujar. Haga esto si necesita una solución

adapter.registerDataSetObserver(new DataSetObserver() {

            @Override
            public void onChanged() {
                super.onChanged();
                removeAndRedrawViews();

            }

        });

2

Hay dos problemas al usar un ListView dentro de un ScrollView.

1- ListView debe expandirse completamente a la altura de sus hijos. este ListView resuelve esto:

public class ListViewExpanded extends ListView
{
    public ListViewExpanded(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        setDividerHeight(0);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST));
    }
}

La altura del divisor debe ser 0, use relleno en las filas en su lugar.

2- El ListView consume eventos táctiles, por lo que ScrollView no se puede desplazar como de costumbre. Este ScrollView resuelve este problema:

public class ScrollViewInterceptor extends ScrollView
{
    float startY;

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e)
    {
        onTouchEvent(e);
        if (e.getAction() == MotionEvent.ACTION_DOWN) startY = e.getY();
        return (e.getAction() == MotionEvent.ACTION_MOVE) && (Math.abs(startY - e.getY()) > 50);
    }
}

¡Esta es la mejor manera que encontré para hacer el truco!


2

Una solución que uso es agregar todo el contenido de ScrollView (lo que debería estar arriba y debajo de listView) como headerView y footerView en ListView.

Así funciona, también se convierte el convertview como debería ser.


1

gracias al código de Vinay, aquí está mi código para cuando no puedes tener una vista de lista dentro de una vista de desplazamiento, pero necesitas algo así

LayoutInflater li = LayoutInflater.from(this);

                RelativeLayout parent = (RelativeLayout) this.findViewById(R.id.relativeLayoutCliente);

                int recent = 0;

                for(Contatto contatto : contatti)
                {
                    View inflated_layout = li.inflate(R.layout.header_listview_contatti, layout, false);


                    inflated_layout.setId(contatto.getId());
                    ((TextView)inflated_layout.findViewById(R.id.textViewDescrizione)).setText(contatto.getDescrizione());
                    ((TextView)inflated_layout.findViewById(R.id.textViewIndirizzo)).setText(contatto.getIndirizzo());
                    ((TextView)inflated_layout.findViewById(R.id.textViewTelefono)).setText(contatto.getTelefono());
                    ((TextView)inflated_layout.findViewById(R.id.textViewMobile)).setText(contatto.getMobile());
                    ((TextView)inflated_layout.findViewById(R.id.textViewFax)).setText(contatto.getFax());
                    ((TextView)inflated_layout.findViewById(R.id.textViewEmail)).setText(contatto.getEmail());



                    RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);

                    if (recent == 0)
                    {
                        relativeParams.addRule(RelativeLayout.BELOW, R.id.headerListViewContatti);
                    }
                    else
                    {
                        relativeParams.addRule(RelativeLayout.BELOW, recent);
                    }
                    recent = inflated_layout.getId();

                    inflated_layout.setLayoutParams(relativeParams);
                    //inflated_layout.setLayoutParams( new RelativeLayout.LayoutParams(source));

                    parent.addView(inflated_layout);
                }

el relativo diseño permanece dentro de una vista de desplazamiento para que todo se pueda desplazar :)


1

Aquí hay una pequeña modificación en la respuesta de @djunod que necesito para que funcione perfectamente:

public static void setListViewHeightBasedOnChildren(ListView listView)
{
    ListAdapter listAdapter = listView.getAdapter();
    if(listAdapter == null) return;
    if(listAdapter.getCount() <= 1) return;

    int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
    int totalHeight = 0;
    View view = null;
    for(int i = 0; i < listAdapter.getCount(); i++)
    {
        view = listAdapter.getView(i, view, listView);
        view.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
        totalHeight += view.getMeasuredHeight();
    }
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}

: Hola, su respuesta funciona la mayor parte del tiempo, pero no funciona si la vista de lista tiene una vista de encabezado y una vista de pie. ¿Podrías revisarlo?
hguser

Necesito una solución cuando los elementos de la lista son de tamaños variables, es decir, una vista de texto que se expande en varias líneas. ¿Cualquier sugerencia?
Khobaib

1

intente esto, esto funciona para mí, olvidé dónde lo encontré, en algún lugar del desbordamiento de la pila, no estoy aquí para explicarle por qué no funciona, pero esta es la respuesta :).

    final ListView AturIsiPulsaDataIsiPulsa = (ListView) findViewById(R.id.listAturIsiPulsaDataIsiPulsa);
    AturIsiPulsaDataIsiPulsa.setOnTouchListener(new ListView.OnTouchListener() 
    {
        @Override
        public boolean onTouch(View v, MotionEvent event) 
        {
            int action = event.getAction();
            switch (action) 
            {
                case MotionEvent.ACTION_DOWN:
                // Disallow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(true);
                break;

                case MotionEvent.ACTION_UP:
                // Allow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(false);
                break;
            }

            // Handle ListView touch events.
            v.onTouchEvent(event);
            return true;
        }
    });
    AturIsiPulsaDataIsiPulsa.setClickable(true);
    AturIsiPulsaDataIsiPulsa.setAdapter(AturIsiPulsaDataIsiPulsaAdapter);

EDITAR!, Finalmente descubrí de dónde saqué el código. aquí ! : ListView dentro de ScrollView no se desplaza en Android


Esto cubre cómo evitar perder la interacción de la vista de desplazamiento, pero la pregunta era sobre cómo mantener la altura de la vista de lista.
Carro abandonado el

1

Aunque los métodos sugeridos setListViewHeightBasedOnChildren () funcionan en la mayoría de los casos, en algunos casos, especialmente con muchos elementos, noté que los últimos elementos no se muestran. Así que decidí imitar una versión simple del comportamiento de ListView para reutilizar cualquier código de Adaptador, aquí está la alternativa de ListView:

import android.content.Context;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ListAdapter;

public class StretchedListView extends LinearLayout {

private final DataSetObserver dataSetObserver;
private ListAdapter adapter;
private OnItemClickListener onItemClickListener;

public StretchedListView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setOrientation(LinearLayout.VERTICAL);
    this.dataSetObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            syncDataFromAdapter();
            super.onChanged();
        }

        @Override
        public void onInvalidated() {
            syncDataFromAdapter();
            super.onInvalidated();
        }
    };
}

public void setAdapter(ListAdapter adapter) {
    ensureDataSetObserverIsUnregistered();

    this.adapter = adapter;
    if (this.adapter != null) {
        this.adapter.registerDataSetObserver(dataSetObserver);
    }
    syncDataFromAdapter();
}

protected void ensureDataSetObserverIsUnregistered() {
    if (this.adapter != null) {
        this.adapter.unregisterDataSetObserver(dataSetObserver);
    }
}

public Object getItemAtPosition(int position) {
    return adapter != null ? adapter.getItem(position) : null;
}

public void setSelection(int i) {
    getChildAt(i).setSelected(true);
}

public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
    this.onItemClickListener = onItemClickListener;
}

public ListAdapter getAdapter() {
    return adapter;
}

public int getCount() {
    return adapter != null ? adapter.getCount() : 0;
}

private void syncDataFromAdapter() {
    removeAllViews();
    if (adapter != null) {
        int count = adapter.getCount();
        for (int i = 0; i < count; i++) {
            View view = adapter.getView(i, null, this);
            boolean enabled = adapter.isEnabled(i);
            if (enabled) {
                final int position = i;
                final long id = adapter.getItemId(position);
                view.setOnClickListener(new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        if (onItemClickListener != null) {
                            onItemClickListener.onItemClick(null, v, position, id);
                        }
                    }
                });
            }
            addView(view);

        }
    }
}
}

1

Todas estas respuestas están mal !!! Si está intentando colocar una vista de lista en una vista de desplazamiento, debe repensar su diseño. Está intentando colocar un ScrollView en un ScrollView. Interferir con la lista perjudicará el rendimiento de la lista. Fue diseñado para ser así por Android.

Si realmente desea que la lista esté en el mismo desplazamiento que los otros elementos, todo lo que tiene que hacer es agregar los otros elementos a la parte superior de la lista utilizando una simple instrucción de cambio en su adaptador:

class MyAdapter extends ArrayAdapter{

    public MyAdapter(Context context, int resource, List objects) {
        super(context, resource, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
         ViewItem viewType = getItem(position);

        switch(viewType.type){
            case TEXTVIEW:
                convertView = layouteInflater.inflate(R.layout.textView1, parent, false);

                break;
            case LISTITEM:
                convertView = layouteInflater.inflate(R.layout.listItem, parent, false);

                break;            }


        return convertView;
    }


}

El adaptador de lista puede manejar todo, ya que solo muestra lo que está visible.


0

Todo este problema desaparecería si LinearLayout tuviera un método setAdapter, porque cuando le dijeras a alguien que lo usara, la alternativa sería trivial.

Si realmente desea un ListView de desplazamiento dentro de otra vista de desplazamiento, esto no ayudará, pero de lo contrario al menos le dará una idea.

Debe crear un adaptador personalizado para combinar todo el contenido sobre el que desea desplazarse y configurar el adaptador de ListView para eso.

No tengo un código de muestra a mano, pero si quieres algo así.

<ListView/>

(other content)

<ListView/>

Luego debe crear un adaptador que represente todo ese contenido. Los ListView / Adapters son lo suficientemente inteligentes como para manejar diferentes tipos también, pero debe escribir el adaptador usted mismo.

La API de la interfaz de usuario de Android simplemente no es tan madura como casi todo lo demás, por lo que no tiene las mismas características que otras plataformas. Además, al hacer algo en Android, debe estar en una mentalidad de Android (Unix) donde espera que para hacer cualquier cosa, probablemente tenga que ensamblar la funcionalidad de partes más pequeñas y escribir un montón de su propio código para obtenerlo trabajo.


0

Cuando lo colocamos ListViewdentro ScrollViewsurgen dos problemas. Uno ScrollViewmide a sus hijos en modo NO ESPECIFICADO, por lo que ListViewestablece su propia altura para acomodar solo un elemento (no sé por qué), otro ScrollViewintercepta el evento táctil paraListView que no se desplace.

Pero nosotros podemos colocar ListViewen el interior ScrollViewcon un poco de solución. Esta publicación , por mí, explica la solución. Con esta solución también podemos conservar ListViewla función de reciclaje.


0

En lugar de colocar la vista de lista dentro de la Vista de desplazamiento, coloque el resto del contenido entre la vista de lista y la apertura de la Vista de desplazamiento como una vista separada y establezca esa vista como el encabezado de la vista de lista. Por lo tanto, finalmente terminará solo con la vista de lista a cargo de Scroll.



-1

Aquí está mi versión del código que calcula la altura total de la vista de lista. Esta funciona para mí:

   public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null || listAdapter.getCount() < 2) {
        // pre-condition
        return;
    }

    int totalHeight = 0;
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(BCTDApp.getDisplaySize().width, View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);
        if (listItem instanceof ViewGroup) listItem.setLayoutParams(lp);
        listItem.measure(widthMeasureSpec, heightMeasureSpec);
        totalHeight += listItem.getMeasuredHeight();
    }

    totalHeight += listView.getPaddingTop() + listView.getPaddingBottom();
    totalHeight += (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight;
    listView.setLayoutParams(params);
    listView.requestLayout();
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.