Come posso inserire un ListView in un ScrollView senza che collassi?


351

Ho cercato soluzioni a questo problema e l'unica risposta che posso trovare sembra essere " non mettere un ListView in uno ScrollView ". Devo ancora vedere una vera spiegazione del perché . L'unica ragione per cui riesco a trovare è che Google non pensa che dovresti volerlo fare. Bene, così ho fatto.

Quindi la domanda è: come si può posizionare un ListView in uno ScrollView senza che collassi alla sua altezza minima?


11
Perché quando un dispositivo utilizza un touchscreen non esiste un buon modo per distinguere tra due contenitori scorrevoli nidificati. Funziona su desktop tradizionali in cui si utilizza la barra di scorrimento per scorrere un contenitore.
Romain Guy,

57
Certo che c'è. Se toccano quello interno, scorri quello. Se quello interno è troppo grande, è una cattiva progettazione dell'interfaccia utente. Ciò non significa che sia una cattiva idea in generale.
DougW,

5
Mi sono appena imbattuto in questo per la mia app per Tablet. Anche se non sembra essere peggio su Smartphone, questo è orribile su un tablet in cui puoi facilmente decidere se l'utente vuole scorrere ScrollView esterno o interno. @DougW: design orribile, sono d'accordo.
medusa,

4
@Romain - questo è un post abbastanza vecchio, quindi mi chiedo se le preoccupazioni qui sono già state affrontate, o non c'è ancora un buon modo per usare un ListView all'interno di un ScrollView (o un'alternativa che non comporta la codifica manuale tutte le bellezze di un ListView in un LinearLayout)?
Jim,

3
@Jim: "o un'alternativa che non comporta la codifica manuale di tutte le bellezze di un ListView in un LinearLayout" - metti tutto all'interno di ListView. ListViewpuò avere più stili di riga, oltre a righe non selezionabili.
Commons War

Risposte:


196

L'uso di a ListViewper non farlo scorrere è estremamente costoso e va contro lo scopo di ListView. NON dovresti farlo. Basta usare un LinearLayoutinvece.


69
La fonte sarei io da quando mi occupo di ListView negli ultimi 2 o 3 anni :) ListView fa molto lavoro extra per ottimizzare l'uso degli adattatori. Cercare di aggirare il problema farà comunque sì che ListView esegua molto lavoro che un LinearLayout non dovrebbe svolgere. Non entrerò nei dettagli perché non c'è abbastanza spazio qui per spiegare l'implementazione di ListView. Anche questo non risolverà il modo in cui ListView si comporta rispetto agli eventi touch. Inoltre, non esiste alcuna garanzia che se il tuo hack funziona oggi funzionerà ancora in una versione futura. L'uso di un LinearLayout non sarebbe davvero molto lavoro.
Romain Guy,

7
Bene, questa è una risposta ragionevole. Se potessi condividere alcuni feedback, avrei un'esperienza iPhone molto più significativa e posso dirti che la documentazione di Apple è molto meglio scritta in merito alle caratteristiche prestazionali e ai casi d'uso (o anti-pattern) come questo. Nel complesso, la documentazione di Android è molto più distribuita e meno focalizzata. Capisco che ci sono alcuni motivi dietro questo, ma questa è una lunga discussione, quindi se ti senti obbligato a parlarne fammelo sapere.
DougW,

6
Si potrebbe facilmente scrivere un metodo statico che crea un LinearLayout da un adattatore.
Romain Guy,

125
Questa NON è una risposta per How can I put a ListView into a ScrollView without it collapsing?... Puoi dire che non dovresti farlo, ma anche dare una risposta su come farlo, questa è una buona risposta. Sai che un ListView ha alcune altre interessanti funzioni oltre allo scorrimento, caratteristiche che LinearLayout non ha.
Jorge Fuentes González,

44
Cosa succede se si dispone di un'area contenente un ListView + altre viste e si desidera che tutto scorra come uno? Sembra un caso d'uso ragionevole per posizionare un ListView all'interno di un ScrollView. Sostituire ListView con LinearLayout non è una soluzione perché non è possibile utilizzare gli adattatori.
Barry Fruitman,

262

Ecco la mia soluzione Sono abbastanza nuovo per la piattaforma Android e sono sicuro che sia un po 'hacker, specialmente nella parte relativa alla chiamata diretta di .misure e all'impostazione LayoutParams.heightdiretta della proprietà, ma funziona.

Tutto quello che devi fare è chiamare Utility.setListViewHeightBasedOnChildren(yourListView)e verrà ridimensionato per adattarsi esattamente all'altezza dei suoi elementi.

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
Hai appena ricreato un LinearLayout molto costoso :)
Romain Guy

82
Tranne LinearLayoutche a non ha tutte le peculiarità intrinseche di a ListView- non ha divisori, viste intestazione / piè di pagina, un selettore di elenco che corrisponde ai colori del tema dell'interfaccia utente del telefono, ecc. Onestamente, non c'è motivo per cui non puoi scorrere il contenitore interno fino a quando non raggiunge la fine e quindi interrompe l'intercettazione degli eventi di tocco in modo che il contenitore esterno scorra. Ogni browser desktop lo fa quando si utilizza la rotella di scorrimento. Android stesso è in grado di gestire lo scorrimento nidificato se si naviga tramite la trackball, quindi perché non tramite touch?
Neil Traft,

29
listItem.measure(0,0)genererà un NPE se listItem è un'istanza di ViewGroup. Ho aggiunto prima quanto segue listItem.measure:if (listItem instanceof ViewGroup) listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
Siklab.ph,

4
Risolto il problema con ListViews con imbottitura diversa da 0:int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
Paul

8
Sfortunatamente questo metodo è un po 'imperfetto quando ListViewpuò contenere oggetti di altezza variabile. La chiamata a getMeasuredHeight()restituisce solo l'altezza minima designata rispetto all'altezza effettiva. Ho provato a utilizzare getHeight()invece, ma questo restituisce 0. Qualcuno ha qualche idea su come affrontare questo?
Matt Huggins,

89

Funzionerà sicuramente ............
Devi semplicemente sostituire il tuo <ScrollView ></ScrollView>file XML in layout con questo Custom ScrollViewtipo <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;
    }
}

4
Questo va molto bene. Permette anche di inserire un frammento di Google Maps all'interno di ScrollView e funziona ancora con lo zoom in / out. Grazie.
Magnus Johansson,

Qualcuno ha notato dei contro con questo approccio? È così semplice che temo: o
Marija Milosevic il

1
Bella soluzione, dovrebbe essere la risposta accettata +1! L'ho mescolato con la risposta qui sotto (da djunod), e funziona benissimo!
Tanou,

1
Questa è l'unica correzione che fa scorrere e FARE CLIC sui bambini che lavorano contemporaneamente per me. Grazie
mille

25

Istituito di mettere ListViewdentro a ScrollView, possiamo usare ListViewcome a ScrollView. Le cose che devono essere dentro ListViewpossono essere messe dentro ListView. Altri layout nella parte superiore e inferiore di ListViewpossono essere inseriti aggiungendo layout all'intestazione e al piè di pagina ListView. Quindi l'intero ListViewti darà un'esperienza di scorrimento.


3
Questa è la risposta più elegante e appropriata. Per maggiori dettagli su questo approccio, vedere questa risposta: stackoverflow.com/a/20475821/1221537
adamdport

21

Ci sono molte situazioni in cui ha molto senso avere ListView in uno ScrollView.

Ecco il codice basato sul suggerimento di DougW ... funziona in un frammento, richiede meno 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();
}

chiama setListViewHeightBasedOnChildren (listview) su ciascuna visualizzazione elenco incorporata.


Non funzionerà quando gli elementi dell'elenco hanno dimensioni variabili, ad esempio alcune visualizzazioni di testo che si espandono su più righe. Qualche soluzione?
Khobaib,

Controllare il mio commento qui - dà la soluzione perfetta - stackoverflow.com/a/23315696/1433187
Khobaib

ha funzionato per me con API 19, ma non con API 23: qui aggiunge molto spazio bianco sotto listView.
Lucker10,

17

ListView in realtà è già in grado di misurarsi per essere abbastanza alto da visualizzare tutti gli elementi, ma non lo fa quando si specifica semplicemente wrap_content (MeasureSpec.UNSPECIFIED). Lo farà quando viene data un'altezza con MeasureSpec.AT_MOST. Con questa conoscenza, è possibile creare una sottoclasse molto semplice per risolvere questo problema che funziona molto meglio di una qualsiasi delle soluzioni sopra pubblicate. Dovresti comunque usare wrap_content con questa sottoclasse.

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

Manipolando heightMeasureSpec su AT_MOST con una dimensione molto grande (Integer.MAX_VALUE >> 4), ListView misura tutti i suoi figli fino all'altezza (molto grande) e imposta la sua altezza di conseguenza.

Funziona meglio delle altre soluzioni per alcuni motivi:

  1. Misura tutto correttamente (imbottitura, divisori)
  2. Misura il ListView durante il passaggio di misura
  3. A causa di # 2, gestisce correttamente le modifiche in larghezza o numero di elementi senza alcun codice aggiuntivo

Il rovescio della medaglia, si potrebbe sostenere che farlo dipende dal comportamento non documentato nell'SDK che potrebbe cambiare. D'altra parte, si potrebbe sostenere che questo è il modo in cui wrap_content dovrebbe davvero funzionare con ListView e che l'attuale comportamento di wrap_content è appena rotto.

Se sei preoccupato che il comportamento possa cambiare in futuro, devi semplicemente copiare la funzione onMeasure e le funzioni correlate da ListView.java e nella tua sottoclasse, quindi eseguire il percorso AT_MOST attraverso onMeasure anche per NON.

A proposito, credo che questo sia un approccio perfettamente valido quando si lavora con un numero limitato di voci di elenco. Può essere inefficiente rispetto a LinearLayout, ma quando il numero di elementi è ridotto, l'utilizzo di LinearLayout è un'ottimizzazione non necessaria e quindi una complessità non necessaria.


Funziona anche con oggetti ad altezza variabile nella visualizzazione elenco!
Denny,

@Jason Y che cosa significa Integer.MAX_VALUE >> 4 significa ?? cosa c'è 4 lì ??
KJEjava48

@Jason Y per me ha funzionato solo dopo aver cambiato Integer.MAX_VALUE >> 2 in Integer.MAX_VALUE >> 21. Perché è così? Funziona anche con ScrollView non con NestedScrollView.
KJEjava48,

13

C'è un'impostazione integrata per questo. Su ScrollView:

android:fillViewport="true"

In Java,

mScrollView.setFillViewport(true);

Romain Guy lo spiega in profondità qui: http://www.curious-creature.org/2010/08/15/scrollviews-handy-trick/


2
Funziona per un LinearLayout come dimostra. Non funziona per un ListView. Sulla base della data di quel post, immagino che l'abbia scritto per fornire un esempio della sua alternativa, ma questo non risponde alla domanda.
DougW

questa è una soluzione rapida

10

Si crea ListView personalizzato che non è scorrevole

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

Nel file delle risorse del layout

<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>

Nel file Java

Crea un oggetto di customListview invece di ListView come: NonScrollListView non_scroll_list = (NonScrollListView) findViewById (R.id.lv_nonscroll_list);


8

Non abbiamo potuto usare due scrolling simultaneamente. Avremo la lunghezza totale di ListView ed espanderemo listview con l'altezza totale. Quindi potremo aggiungere ListView in ScrollView direttamente o usando LinearLayout perché ScrollView ha direttamente un figlio. copia il metodo setListViewHeightBasedOnChildren (lv) nel tuo codice ed espandi listview quindi puoi usare listview in scrollview. \ 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>

Metodo onCreate nella classe Activity:

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

Buona soluzione! Funziona secondo necessità. Grazie. Nel mio caso ho un sacco di viste prima dell'elenco e la soluzione perfetta è quella di avere solo uno scorrimento verticale sulla vista.
Proverbio,

4

Questa è una combinazione delle risposte di DougW, Good Guy Greg e Paul. Ho scoperto che era tutto necessario quando si è tentato di utilizzarlo con un adattatore listview personalizzato e voci di elenco non standard, altrimenti la visualizzazione in elenco ha causato il crash dell'applicazione (anche con la risposta di 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);
    }

Dove chiamare questo metodo?
Dhrupal,

Dopo aver creato la visualizzazione elenco e aver impostato l'adattatore.
Carrello abbandonato

Questo diventa troppo lento su una lunga lista.
cakan,

@cakan È stato anche scritto 2 anni prima come soluzione alternativa per consentire l'utilizzo insieme di determinati componenti dell'interfaccia utente che non sono realmente pensati per essere combinati. Stai aggiungendo una quantità non necessaria di sovraccarico nel moderno Android, quindi è prevedibile che l'elenco rallenti quando diventa grande.
Carrello abbandonato

3

Non inserire un ListView in un ScrollView perché un ListView è già un ScrollView. Quindi sarebbe come inserire un ScrollView in un ScrollView.

Cosa stai cercando di realizzare?


4
Sto cercando di ottenere un ListView all'interno di uno ScrollView. Non voglio che il mio ListView sia scorrevole, ma non esiste una proprietà semplice per impostare myListView.scrollEnabled = false;
DougW,

Come diceva Romain Guy, questo non è assolutamente lo scopo di un ListView. Un ListView è una vista ottimizzata per la visualizzazione di un lungo elenco di elementi, con molte logiche complicate per eseguire il caricamento lento di viste, riutilizzare viste, ecc. Niente di tutto ciò ha senso se si dice a ListView di non scorrere. Se vuoi solo un gruppo di elementi in una colonna verticale, usa un LinearLayout.
Cheryl Simon,

9
Il problema è che esistono diverse soluzioni fornite da ListView, inclusa quella citata. Gran parte delle funzionalità semplificate dagli adattatori riguarda la gestione e il controllo dei dati, non l'ottimizzazione dell'interfaccia utente. IMO dovrebbe esserci stato un semplice ListView e uno più complesso che fa riutilizzare tutti gli elementi dell'elenco e altro.
DougW,

2
Assolutamente d'accordo. Si noti che è facile utilizzare un adattatore esistente con un LinearLayout, ma voglio tutte le cose belle fornite da ListView, come i divisori, e che non ho voglia di implementare manualmente.
Artem Russakovskii,

1
@ArtemRussakovskii Puoi spiegare il tuo modo semplice per sostituire ListView a LinearLayout con un adattatore esistente?
happyhardik,

3

Ho convertito @ DougW's Utilityin C # (usato in Xamarin). Quanto segue funziona bene per gli elementi ad altezza fissa nell'elenco e sarà per lo più bene, o almeno un buon inizio, se solo alcuni degli articoli sono un po 'più grandi dell'elemento standard.

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

Grazie @DougW, questo mi ha costretto a lavorare quando ho dovuto lavorare con OtherPeople'sCode. :-)


Una domanda: quando hai chiamato il setListViewHeightBasedOnChildren()metodo? Perché non funziona sempre per me.
Alexandre D.

@AlexandreD si chiama Utility.setListViewHeightBasedOnChildren (yourListView) dopo aver creato l'elenco di elementi. cioè assicurati di aver creato prima la tua lista! Avevo scoperto che stavo facendo un elenco e stavo persino visualizzando i miei oggetti in Console.WriteLine ... ma in qualche modo gli oggetti erano nell'ambito sbagliato. Una volta capito e assicurato di avere la giusta portata per la mia lista, questo ha funzionato come un fascino.
Phil Ryan,

Il fatto è che le mie liste sono create nel mio ViewModel. Come posso aspettare che i miei elenchi vengano creati nella mia vista prima di qualsiasi chiamata a Utility.setListViewHeightBasedOnChildren (yourListView)?
Alexandre D.

3

Questa è l'unica cosa che ha funzionato per me:

da Lollipop in poi puoi usare

yourtListView.setNestedScrollingEnabled(true);

Questo abilita o disabilita lo scorrimento nidificato per questa vista se è necessaria la retrocompatibilità con la versione precedente del sistema operativo, è necessario utilizzare RecyclerView.


Ciò non ha avuto alcun effetto sulle visualizzazioni del mio elenco sull'API 22 in un DialogFragment in un'attività AppCompat. Crollano ancora. @Phil Ryan 's risposta ha lavorato con un po' di modifica.
Hakan Çelik MK1,

3

Prima non era possibile. Ma con il rilascio di nuove librerie Appcompat e librerie di design, questo può essere ottenuto.

Devi solo usare NestedScrollView https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html

Non sono a conoscenza che funzionerà con Listview o meno, ma funziona con RecyclerView.

Snippet di codice:

<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>

Fantastico, non l'ho provato, quindi non posso confermare che un ListView interno non collassi, ma sembra che potrebbe essere l'intento. Profondamente rattristato di non aver visto Romain Guy nel diario della colpa;)
DougW,

1
Funziona con recyclerView ma non listView. Grazie
IgniteCoders il

2

ehi ho avuto un problema simile. Volevo visualizzare una vista elenco che non scorreva e ho scoperto che la manipolazione dei parametri ha funzionato ma era inefficiente e si sarebbe comportata in modo diverso su diversi dispositivi .. di conseguenza, questo è un pezzo del mio codice di pianificazione che lo fa in modo molto efficiente .

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: se lo usi, notifyDataSetChanged();non funzionerà come previsto poiché le viste non verranno ridisegnate. Fallo se hai bisogno di una soluzione

adapter.registerDataSetObserver(new DataSetObserver() {

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

            }

        });

2

Esistono due problemi quando si utilizza un ListView all'interno di un ScrollView.

1- ListView deve espandersi completamente all'altezza dei suoi figli. questo ListView risolve questo:

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

L'altezza del divisore deve essere 0, utilizzare invece il riempimento in righe.

2- ListView utilizza gli eventi touch, quindi ScrollView non può essere fatto scorrere normalmente. Questo ScrollView risolve questo 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);
    }
}

Questo è il modo migliore che ho trovato per fare il trucco!


2

Una soluzione che uso è quella di aggiungere tutto il contenuto di ScrollView (ciò che dovrebbe essere sopra e sotto listView) come headerView e footerView in ListView.

Quindi funziona come, anche convertview viene ridimensionato come dovrebbe essere.


1

grazie al codice di Vinay ecco il mio codice per quando non è possibile avere una visualizzazione elenco all'interno di una visualizzazione scorrimento, ma è necessario qualcosa del genere

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

il relativo Relay rimane all'interno di un ScrollView in modo che tutto diventa scorrevole :)


1

Ecco piccola modifica sul @djunod 's risposta che ho bisogno di farlo funzionare perfettamente:

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

: Ciao, la tua risposta funziona la maggior parte delle volte, ma non funziona se la vista elenco ha una vista intestazione e una vista piede. Potresti controllarlo?
hguser,

Ho bisogno di una soluzione quando gli elementi dell'elenco hanno dimensioni variabili, ad esempio alcune visualizzazioni di testo che si espandono su più righe. Qualche suggerimento?
Khobaib,

1

prova questo, questo funziona per me, ho dimenticato dove l'ho trovato, da qualche parte nello stack overflow, non sono qui per spiegarlo perché non funziona, ma questa è la risposta :).

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

EDIT !, ho finalmente scoperto dove ho ottenuto il codice. Qui ! : ListView in ScrollView non scorre su Android


Questo spiega come evitare di perdere l'interazione con scrollview, ma la domanda riguardava il mantenimento dell'altezza della viewlist.
Carrello abbandonato

1

Sebbene i metodi suggeriti setListViewHeightBasedOnChildren () funzionino nella maggior parte dei casi, in alcuni casi, specialmente con molti elementi, ho notato che gli ultimi elementi non sono visualizzati. Quindi ho deciso di imitare una versione semplice del comportamento di ListView per riutilizzare qualsiasi codice Adapter, ecco l'alternativa a 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

Tutte queste risposte sono sbagliate !!! Se stai provando a mettere una visualizzazione elenco in una vista di scorrimento, dovresti ripensare il tuo progetto. Stai tentando di inserire uno ScrollView in uno ScrollView. Interferire con l'elenco danneggerà le prestazioni dell'elenco. È stato progettato per essere così su Android.

Se vuoi davvero che l'elenco si trovi nello stesso scorrimento degli altri elementi, tutto ciò che devi fare è aggiungere gli altri elementi in cima all'elenco usando una semplice istruzione switch nel tuo adattatore:

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


}

L'adattatore elenco può gestire tutto poiché rende solo ciò che è visibile.


0

L'intero problema sarebbe sparito se LinearLayout avesse un metodo setAdapter, perché quando dicevi a qualcuno di usarlo invece l'alternativa sarebbe banale.

Se vuoi davvero un ListView a scorrimento all'interno di un'altra vista a scorrimento, questo non ti aiuterà, ma altrimenti ti darà almeno un'idea.

È necessario creare un adattatore personalizzato per combinare tutto il contenuto che si desidera scorrere e impostare l'adattatore ListView su quello.

Non ho un codice di esempio a portata di mano, ma se vuoi qualcosa del genere.

<ListView/>

(other content)

<ListView/>

Quindi è necessario creare un adattatore che rappresenti tutto quel contenuto. ListView / Adapters sono abbastanza intelligenti da gestire anche tipi diversi, ma è necessario scrivere l'adattatore da soli.

L'API dell'interfaccia utente Android non è così matura come praticamente tutto il resto là fuori, quindi non ha le stesse qualità di altre piattaforme. Inoltre, quando fai qualcosa su Android devi essere in una mentalità android (unix) in cui ti aspetti che per fare qualsiasi cosa probabilmente dovrai assemblare funzionalità di parti più piccole e scrivere un sacco di codice per ottenerlo lavoro.


0

Quando mettiamo ListViewdentro ScrollViewsorgono due problemi. Uno è ScrollViewmisurare i propri figli in modalità ListViewNon-corrente , quindi imposta la propria altezza per accogliere solo un oggetto (non so perché), un altro è ScrollViewintercettare l'evento touch quindi ListViewnon scorre.

Ma siamo in grado di mettere ListViewdentro ScrollViewcon una certa soluzione. Questo post , da parte mia, spiega la soluzione alternativa. Con questa soluzione possiamo anche mantenere ListViewla funzione di riciclaggio.


0

Invece di inserire la visualizzazione elenco all'interno di Scrollview, inserire il resto del contenuto tra listview e l'apertura di Scrollview come vista separata e impostare quella vista come intestazione della vista elenco. Quindi, alla fine, finirai solo con la visualizzazione dell'elenco prendendo in carico Scroll.



-1

Ecco la mia versione del codice che calcola l'altezza totale della vista elenco. Questo funziona per me:

   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();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.