Comment puis-je mettre un ListView dans un ScrollView sans qu'il ne s'effondre?

351

J'ai cherché des solutions à ce problème, et la seule réponse que je peux trouver semble être " ne mettez pas un ListView dans un ScrollView ". Je n'ai pas encore vu d'explication réelle pour expliquer pourquoi . La seule raison pour laquelle je peux sembler trouver est que Google ne pense pas que vous devriez vouloir faire cela. Eh bien, je l'ai fait.

La question est donc de savoir comment placer un ListView dans un ScrollView sans qu'il ne s'effondre à sa hauteur minimale?

DougW
la source
11
Parce que lorsqu'un appareil utilise un écran tactile, il n'y a aucun bon moyen de différencier deux conteneurs déroulants imbriqués. Il fonctionne sur les bureaux traditionnels où vous utilisez la barre de défilement pour faire défiler un conteneur.
Romain Guy
57
Bien sûr que oui. S'ils se touchent à l'intérieur, faites défiler cela. Si l'intérieur est trop grand, c'est une mauvaise conception de l'interface utilisateur. Cela ne signifie pas que c'est une mauvaise idée en général.
DougW
5
Je suis juste tombé sur cela pour mon application Tablet. Bien que cela ne semble pas être pire sur smartphone, c'est horrible sur une tablette où vous pouvez facilement décider si l'utilisateur veut faire défiler le ScrollView externe ou interne. @DougW: Conception horrible, je suis d'accord.
méduse
4
@Romain - il s'agit d'un article assez ancien, donc je me demande si les problèmes ici ont déjà été résolus, ou s'il n'y a toujours pas de bon moyen d'utiliser un ListView à l'intérieur d'un ScrollView (ou une alternative qui n'implique pas de codage manuel) toutes les subtilités d'un ListView dans un LinearLayout)?
Jim
3
@Jim: "ou une alternative qui n'implique pas de coder manuellement toutes les subtilités d'un ListView dans un LinearLayout" - mettez tout à l'intérieur du ListView. ListViewpeut avoir plusieurs styles de ligne, plus des lignes non sélectionnables.
CommonsWare

Réponses:

196

Utiliser un ListViewpour ne pas faire défiler est extrêmement coûteux et va à l'encontre de l'objectif de ListView. Vous ne devriez PAS faire cela. Utilisez simplement un à la LinearLayoutplace.

Romain Guy
la source
69
La source serait moi puisque je suis en charge de ListView depuis 2 ou 3 ans :) ListView fait beaucoup de travail supplémentaire pour optimiser l'utilisation des adaptateurs. Essayer de contourner cela obligera toujours ListView à faire beaucoup de travail qu'un LinearLayout n'aurait pas à faire. Je n'entrerai pas dans les détails car il n'y a pas assez de place ici pour expliquer l'implémentation de ListView. Cela ne résoudra pas non plus le comportement de ListView par rapport aux événements tactiles. Il n'y a également aucune garantie que si votre hack fonctionne aujourd'hui, il fonctionnera toujours dans une future version. Utiliser un LinearLayout ne serait pas vraiment beaucoup de travail.
Romain Guy
7
Eh bien, c'est une réponse raisonnable. Si je pouvais partager quelques commentaires, j'ai une expérience iPhone beaucoup plus importante et je peux vous dire que la documentation d'Apple est bien mieux écrite en ce qui concerne les caractéristiques de performance et les cas d'utilisation (ou anti-modèles) comme celui-ci. Dans l'ensemble, la documentation Android est beaucoup plus distribuée et moins ciblée. Je comprends qu'il y ait des raisons derrière cela, mais c'est une longue discussion, donc si vous vous sentez obligé d'en discuter, faites le moi savoir.
DougW
6
Vous pouvez facilement écrire une méthode statique qui crée un LinearLayout à partir d'un adaptateur.
Romain Guy
125
Ce n'est PAS une réponse pour How can I put a ListView into a ScrollView without it collapsing?... Vous pouvez dire que vous ne devriez pas le faire, mais aussi donner une réponse sur la façon de le faire, c'est une bonne réponse. Vous savez qu'un ListView a d'autres fonctionnalités intéressantes en dehors du défilement, des fonctionnalités que LinearLayout n'a pas.
Jorge Fuentes González
44
Que se passe-t-il si vous avez une région contenant un ListView + d'autres vues et que vous voulez que tout défile en un? Cela semble être un cas d'utilisation raisonnable pour placer un ListView dans un ScrollView. Remplacer le ListView par un LinearLayout n'est pas une solution car vous ne pouvez alors pas utiliser d'adaptateurs.
Barry Fruitman
262

Voici ma solution. Je suis assez nouveau sur la plate-forme Android, et je suis sûr que c'est un peu piraté, en particulier dans la partie concernant l'appel direct de .measure et la définition LayoutParams.heightdirecte de la propriété, mais cela fonctionne.

Tout ce que vous avez à faire est d'appeler Utility.setListViewHeightBasedOnChildren(yourListView)et il sera redimensionné pour s'adapter exactement à la hauteur de ses articles.

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);
    }
}
DougW
la source
66
Vous venez de recréer un LinearLayout très cher :)
Romain Guy
82
Sauf que a LinearLayoutn'a pas toutes les subtilités inhérentes à a ListView- il n'a pas de séparateurs, de vues en-tête / pied de page, un sélecteur de liste qui correspond aux couleurs du thème de l'interface utilisateur du téléphone, etc. Honnêtement, il n'y a aucune raison pour laquelle vous ne pouvez pas faire défiler le conteneur interne jusqu'à ce qu'il atteigne la fin, puis faites-le cesser d'intercepter les événements tactiles afin que le conteneur externe défile. Chaque navigateur de bureau le fait lorsque vous utilisez votre molette de défilement. Android lui-même peut gérer le défilement imbriqué si vous naviguez via le trackball, alors pourquoi pas via le toucher?
Neil Traft
29
listItem.measure(0,0)va lancer un NPE si listItem est une instance de ViewGroup. J'ai ajouté ce qui suit avant listItem.measure:if (listItem instanceof ViewGroup) listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
Siklab.ph
4
Correction de ListViews avec un rembourrage autre que 0:int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
Paul
8
Malheureusement, cette méthode est un peu défectueuse lorsqu'elle ListViewpeut contenir des éléments de hauteur variable. L'appel à getMeasuredHeight()renvoie uniquement la hauteur minimale ciblée par opposition à la hauteur réelle. J'ai essayé d'utiliser à la getHeight()place, mais cela renvoie 0. Quelqu'un at-il une idée de la façon d'aborder cela?
Matt Huggins
89

Cela fonctionnera certainement ............
Vous devez simplement remplacer votre <ScrollView ></ScrollView>fichier XML de mise en page par ceci Custom ScrollViewcomme <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;
    }
}
Atul Bhardwaj
la source
4
C'est très bien. Il permet également de mettre un fragment Google Maps à l'intérieur de ScrollView, et cela fonctionne toujours avec un zoom avant / arrière. Merci.
Magnus Johansson
Quelqu'un a-t-il remarqué des inconvénients à cette approche? C'est si simple que j'ai peur: o
Marija Milosevic
1
Belle solution, devrait être la réponse acceptée +1! Je l'ai mélangé avec la réponse ci-dessous (de djunod), et cela fonctionne très bien!
tanou
1
C'est le seul correctif qui fait défiler ET CLIQUER sur les enfants qui travaillent en même temps pour moi. Un grand merci
pellyadolfo
25

Au lieu de mettre à l' ListViewintérieur d'un ScrollView, on peut l'utiliser ListViewcomme un ScrollView. Les objets qui doivent ListViewêtre placés peuvent être placés à l'intérieur du ListView. D'autres dispositions en haut et en bas de ListViewpeuvent être ajoutées en ajoutant des dispositions à l'en-tête et au pied de page de ListView. Ainsi, l'ensemble ListViewvous donnera une expérience de défilement.

Arun
la source
3
C'est la réponse la plus élégante et la plus appropriée à mon humble avis. Pour plus de détails sur cette approche, voir cette réponse: stackoverflow.com/a/20475821/1221537
adamdport
21

Il existe de nombreuses situations où il est très logique d'avoir ListView dans un ScrollView.

Voici le code basé sur la suggestion de DougW ... fonctionne dans un fragment, prend moins de mémoire.

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

appelez setListViewHeightBasedOnChildren (listview) sur chaque listview incorporée.

djunod
la source
Cela ne fonctionnera pas lorsque les éléments de liste sont de tailles variables, c'est-à-dire une vue texte qui s'étend sur plusieurs lignes. Toute solution?
Khobaib
Vérifiez mon commentaire ici - il donne la solution parfaite - stackoverflow.com/a/23315696/1433187
Khobaib
a fonctionné pour moi avec l'API 19, mais pas avec l'API 23: Ici, il ajoute beaucoup d'espace blanc en dessous de la listView.
Lucker10
17

ListView est en fait déjà capable de se mesurer pour être suffisamment grand pour afficher tous les éléments, mais il ne le fait pas lorsque vous spécifiez simplement wrap_content (MeasureSpec.UNSPECIFIED). Il le fera lorsqu'il recevra une hauteur avec MeasureSpec.AT_MOST. Avec ces connaissances, vous pouvez créer une sous-classe très simple pour résoudre ce problème qui fonctionne bien mieux que toutes les solutions publiées ci-dessus. Vous devez toujours utiliser wrap_content avec cette sous-classe.

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 manipulation de heightMeasureSpec pour être AT_MOST avec une très grande taille (Integer.MAX_VALUE >> 4) oblige le ListView à mesurer tous ses enfants jusqu'à la hauteur donnée (très grande) et à définir sa hauteur en conséquence.

Cela fonctionne mieux que les autres solutions pour plusieurs raisons:

  1. Il mesure tout correctement (rembourrage, séparateurs)
  2. Il mesure le ListView pendant la passe de mesure
  3. En raison de # 2, il gère correctement les changements de largeur ou de nombre d'articles sans code supplémentaire

À la baisse, vous pourriez faire valoir que cela dépend de comportements non documentés dans le SDK qui pourraient changer. D'un autre côté, vous pourriez affirmer que c'est ainsi que wrap_content devrait vraiment fonctionner avec ListView et que le comportement actuel de wrap_content est simplement rompu.

Si vous craignez que le comportement puisse changer à l'avenir, vous devez simplement copier la fonction onMeasure et les fonctions connexes à partir de ListView.java et dans votre propre sous-classe, puis faire également le chemin AT_MOST via onMeasure pour UNSPECIFIED.

Soit dit en passant, je pense que c'est une approche parfaitement valable lorsque vous travaillez avec un petit nombre d'éléments de liste. Il peut être inefficace par rapport à LinearLayout, mais lorsque le nombre d'éléments est petit, l'utilisation de LinearLayout est une optimisation inutile et donc une complexité inutile.

Jason Y
la source
Fonctionne également avec les éléments à hauteur variable dans la liste de visualisation!
Denny
@Jason Y qu'est-ce que cela fait, Integer.MAX_VALUE >> 4 signifie ?? qu'est-ce que 4 là ??
KJEjava48
@Jason Y pour moi, cela n'a fonctionné qu'après avoir changé Integer.MAX_VALUE >> 2 en Integer.MAX_VALUE >> 21. Pourquoi en est-il ainsi? Cela fonctionne également avec ScrollView et non avec NestedScrollView.
KJEjava48
13

Il y a un paramètre intégré pour cela. Sur le ScrollView:

android:fillViewport="true"

En Java,

mScrollView.setFillViewport(true);

Romain Guy l'explique en détail ici: http://www.curious-creature.org/2010/08/15/scrollviews-handy-trick/

TalkLittle
la source
2
Cela fonctionne pour un LinearLayout comme il le démontre. Cela ne fonctionne pas pour un ListView. Sur la base de la date de ce post, j'imagine qu'il l'a écrit pour donner un exemple de son alternative, mais cela ne répond pas à la question.
DougW
c'est une solution rapide
10

Vous créez une ListView personnalisée qui n'est pas défilable

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

Dans votre fichier de ressources de mise en page

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

Dans un fichier Java

Créez un objet de votre customListview au lieu de ListView comme: NonScrollListView non_scroll_list = (NonScrollListView) findViewById (R.id.lv_nonscroll_list);

Dedaniya HirenKumar
la source
8

Nous ne pourrions pas utiliser deux défilements simultanés. Nous aurons la longueur totale de ListView et développerons listview avec la hauteur totale. Ensuite, nous pouvons ajouter ListView dans ScrollView directement ou en utilisant LinearLayout car ScrollView a directement un enfant. copiez la méthode setListViewHeightBasedOnChildren (lv) dans votre code et développez listview puis vous pouvez utiliser listview dans 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>

Méthode onCreate dans la 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);
      }
Ashish Saini
la source
Bonne solution! Cela fonctionne au besoin. Merci. Dans mon cas, j'ai tout un tas de vues avant la liste et la solution parfaite est d'avoir un seul défilement vertical sur la vue.
Proverbio
4

Ceci est une combinaison des réponses de DougW, Good Guy Greg et Paul. J'ai trouvé que tout était nécessaire lorsque j'essayais de l'utiliser avec un adaptateur listview personnalisé et des éléments de liste non standard, sinon listview plantait l'application (également plantée avec la réponse 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);
    }
Panier abandonné
la source
Où appeler cette méthode?
Dhrupal
Après avoir créé la vue de liste et configuré l'adaptateur.
Panier abandonné
Cela devient trop lent sur de longues listes.
cakan
@cakan Il a également été écrit 2 ans plus tôt comme solution de contournement pour permettre d'utiliser certains composants d'interface utilisateur ensemble qui ne sont pas vraiment destinés à être combinés. Vous ajoutez une quantité inutile de frais généraux dans Android moderne, il faut donc s'attendre à ce que la liste ralentisse lorsqu'elle devient grande.
Panier abandonné le
3

Vous ne devez pas placer un ListView dans un ScrollView car un ListView est déjà un ScrollView. Ce serait donc comme mettre un ScrollView dans un ScrollView.

Qu'est-ce que vous essayez d'accomplir?

Cheryl Simon
la source
4
J'essaie d'accomplir un ListView dans un ScrollView. Je ne veux pas que mon ListView puisse défiler, mais il n'y a pas de propriété simple pour définir myListView.scrollEnabled = false;
DougW
Comme Romain Guy l'a dit, ce n'est pas du tout le but d'un ListView. Une ListView est une vue optimisée pour afficher une longue liste d'éléments, avec beaucoup de logique compliquée pour effectuer un chargement paresseux des vues, réutiliser des vues, etc. Rien de tout cela n'a de sens si vous dites à ListView de ne pas faire défiler. Si vous voulez juste un tas d'éléments dans une colonne verticale, utilisez un LinearLayout.
Cheryl Simon
9
Le problème est qu'il existe un certain nombre de solutions fournies par ListView, y compris celle que vous mentionnez. Cependant, la plupart des fonctionnalités simplifiées par les adaptateurs concernent la gestion et le contrôle des données, et non l'optimisation de l'interface utilisateur. OMI, il aurait dû y avoir un simple ListView, et un plus complexe qui fait toute la réutilisation des éléments de la liste et tout ça.
DougW
2
Absolument d'accord. Notez qu'il est facile d'utiliser un adaptateur existant avec un LinearLayout, mais je veux toutes les belles choses que ListView fournit, comme des séparateurs, et que je n'ai pas envie de mettre en œuvre manuellement.
Artem Russakovskii
1
@ArtemRussakovskii Pouvez-vous expliquer comment remplacer facilement ListView à LinearLayout par un adaptateur existant?
happyhardik
3

J'ai converti @ DougW's Utilityen C # (utilisé dans Xamarin). Ce qui suit fonctionne très bien pour les éléments à hauteur fixe dans la liste, et sera généralement très bien, ou du moins un bon début, si seulement certains des éléments sont un peu plus gros que l'élément 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));
        }
    }
}

Merci @DougW, cela m'a permis de sortir d'une situation difficile lorsque je devais travailler avec OtherPeople'sCode. :-)

Phil Ryan
la source
Une question: quand avez-vous appelé la setListViewHeightBasedOnChildren()méthode? Parce que ça ne marche pas tout le temps pour moi.
Alexandre D.
@AlexandreD vous appelez Utility.setListViewHeightBasedOnChildren (yourListView) lorsque vous avez créé la liste des éléments. c'est-à-dire assurez-vous que vous avez créé votre liste en premier! J'avais constaté que je faisais une liste, et j'affichais même mes éléments dans Console.WriteLine ... mais d'une manière ou d'une autre, les éléments n'étaient pas dans la bonne portée. Une fois que j'ai compris cela et que je me suis assuré que j'avais la bonne portée pour ma liste, cela a fonctionné comme un charme.
Phil Ryan
Le fait est que mes listes sont créées dans mon ViewModel. Comment puis-je attendre que mes listes soient créées dans ma vue avant tout appel à Utility.setListViewHeightBasedOnChildren (yourListView)?
Alexandre D.
3

C'est la seule chose qui a fonctionné pour moi:

sur Lollipop, vous pouvez utiliser

yourtListView.setNestedScrollingEnabled(true);

Cela active ou désactive le défilement imbriqué pour cette vue si vous avez besoin d'une compatibilité descendante avec une ancienne version du système d'exploitation, vous devrez utiliser RecyclerView.

JackA
la source
Cela n'a eu aucun effet sur mes vues de liste sur l'API 22 dans un DialogFragment dans une activité AppCompat. Ils s'effondrent toujours. De Ryan @Phil réponse a travaillé avec un peu de modification.
Hakan Çelik MK1
3

Avant, ce n'était pas possible. Mais avec la sortie de nouvelles bibliothèques Appcompat et de bibliothèques de conception, cela peut être réalisé.

Il vous suffit d'utiliser NestedScrollView https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html

Je ne sais pas si cela fonctionnera avec Listview ou non, mais fonctionne avec RecyclerView.

Extrait de code:

<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>
Shashank Kapsime
la source
Cool, je ne l'ai pas essayé donc je ne peux pas confirmer qu'un ListView interne ne s'effondre pas, mais il semble que ce soit l'intention. Profondément attristé de ne pas voir Romain Guy dans le journal des reproches;)
DougW
1
Cela fonctionne avec recyclerView mais pas listView. Thanx
IgniteCoders
2

hey j'avais un problème similaire. Je voulais afficher une vue de liste qui ne défilait pas et j'ai trouvé que la manipulation des paramètres fonctionnait mais était inefficace et se comporterait différemment sur différents appareils .. en conséquence, c'est un morceau de mon code de programmation qui le fait en fait très efficacement .

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

Remarque: si vous utilisez cela, notifyDataSetChanged();ne fonctionnera pas comme prévu car les vues ne seront pas redessinées. Faites-le si vous avez besoin d'une solution

adapter.registerDataSetObserver(new DataSetObserver() {

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

            }

        });
PSchuette
la source
2

Il existe deux problèmes lors de l'utilisation d'un ListView dans un ScrollView.

1- ListView doit s'étendre complètement à la hauteur de ses enfants. ce ListView résoudre ce problème:

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 hauteur du séparateur doit être égale à 0, utilisez plutôt un remplissage dans les lignes.

2- Le ListView consomme des événements tactiles afin que ScrollView ne puisse pas défiler comme d'habitude. Ce ScrollView résout ce problème:

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

C'est le meilleur moyen que j'ai trouvé pour faire l'affaire!

Ali
la source
2

Une solution que j'utilise est d'ajouter tout le contenu du ScrollView (ce qui devrait être au-dessus et en dessous du listView) en tant que headerView et footerView dans le ListView.

Donc, cela fonctionne comme, la convertview est également redéfinie comme elle devrait être.

brokedid
la source
1

grâce au code de Vinay voici mon code pour quand vous ne pouvez pas avoir une vue de liste dans une vue de défilement mais vous avez besoin de quelque chose comme ça

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

le relativeLayout reste à l'intérieur d'un ScrollView donc tout devient scrollable :)

max4ever
la source
1

Voici une petite modification sur la réponse de @djunod dont j'ai besoin pour le faire fonctionner parfaitement:

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();
}
Eng.Fouad
la source
: Bonjour, votre réponse fonctionne la plupart du temps, mais elle ne fonctionne pas si la vue de liste a une vue d'en-tête et une vue de pied. Pourriez-vous le vérifier?
hguser
J'ai besoin d'une solution lorsque les éléments de liste sont de tailles variables, c'est-à-dire une vue de texte qui s'étend sur plusieurs lignes. Toute suggestion?
Khobaib
1

essayez ceci, cela fonctionne pour moi, j'ai oublié où je l'ai trouvé, quelque part dans le débordement de pile, je ne suis pas ici pour expliquer pourquoi cela ne fonctionne pas, mais c'est la réponse :).

    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!, J'ai finalement découvert où j'avais obtenu le code. ici ! : ListView dans ScrollView ne défile pas sur Android

Bhimbim
la source
Cela explique comment éviter de perdre l'interaction scrollview, mais la question était de maintenir la hauteur de listview.
Panier abandonné
1

Bien que les méthodes setListViewHeightBasedOnChildren () suggérées fonctionnent dans la plupart des cas, dans certains cas, spécialement avec beaucoup d'éléments, j'ai remarqué que les derniers éléments ne sont pas affichés. J'ai donc décidé d'imiter une version simple du comportement ListView afin de réutiliser n'importe quel code adaptateur, voici l'alternative 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);

        }
    }
}
}
Alécio Carvalho
la source
1

Toutes ces réponses sont fausses !!! Si vous essayez de mettre une vue de liste dans une vue de défilement, vous devez repenser votre conception. Vous essayez de mettre un ScrollView dans un ScrollView. Interférer avec la liste nuira aux performances de la liste. Il a été conçu pour être comme ça par Android.

Si vous voulez vraiment que la liste soit dans le même défilement que les autres éléments, tout ce que vous avez à faire est d'ajouter les autres éléments en haut de la liste à l'aide d'une simple instruction switch dans votre adaptateur:

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'adaptateur de liste peut tout gérer car il ne rend que ce qui est visible.

TacoEater
la source
0

Tout ce problème disparaîtrait si LinearLayout avait une méthode setAdapter, car alors, lorsque vous avez dit à quelqu'un de l'utiliser à la place, l'alternative serait triviale.

Si vous voulez réellement un ListView défilant dans une autre vue défilante, cela ne vous aidera pas, mais sinon cela vous donnera au moins une idée.

Vous devez créer un adaptateur personnalisé pour combiner tout le contenu que vous souhaitez faire défiler et définir l'adaptateur de ListView sur celui-ci.

Je n'ai pas d'exemple de code à portée de main, mais si vous voulez quelque chose comme.

<ListView/>

(other content)

<ListView/>

Ensuite, vous devez créer un adaptateur qui représente tout ce contenu. Les ListView / Adapters sont suffisamment intelligents pour gérer également différents types, mais vous devez écrire l'adaptateur vous-même.

L'API Android UI n'est tout simplement pas aussi mature que tout le reste, donc elle n'a pas les mêmes subtilités que les autres plates-formes. De plus, lorsque vous faites quelque chose sur Android, vous devez être dans un état d'esprit Android (Unix) où vous vous attendez à ce que pour faire quoi que ce soit, vous devrez probablement assembler des fonctionnalités de petites pièces et écrire un tas de votre propre code pour l'obtenir. travail.

majinnaibu
la source
0

Lorsque nous nous plaçons à l' ListViewintérieur, ScrollViewdeux problèmes surgissent. L'un ScrollViewmesure ses enfants en mode NON SPÉCIFIÉ, ListViewdéfinit donc sa propre hauteur pour accueillir un seul élément (je ne sais pas pourquoi), un autre ScrollViewintercepte l'événement tactile et ListViewne défile pas.

Mais nous pouvons placer à l' ListViewintérieur ScrollViewavec une solution de contournement. Ce message , par moi, explique la solution de contournement. Grâce à cette solution de contournement, nous pouvons également conserver ListViewla fonction de recyclage de.

Durgadass S
la source
0

Au lieu de placer la vue de liste dans Scrollview, placez le reste du contenu entre la vue de liste et l'ouverture de Scrollview en tant que vue distincte et définissez cette vue comme en-tête de la vue de liste. Vous finirez donc par ne voir que la liste en prenant en charge Scroll.

Saksham
la source
0

Cette bibliothèque est la solution la plus simple et la plus rapide au problème.

Kashif Nazar
la source
-1

Voici ma version du code qui calcule la hauteur totale de la vue liste. Celui-ci fonctionne pour moi:

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