web-dev-qa-db-fra.com

Utilisation d'un ListAdapter pour remplir un LinearLayout dans une présentation ScrollView

Je suis confronté à un problème très courant: J'ai organisé une activité et il s’avère qu’il devrait afficher quelques éléments dans cette ScrollView. La manière normale de le faire serait d'utiliser la ListAdapter existante, de la connecter à une ListView et à un BOOM. J'aurais ma liste d'éléments.

MAISVous ne devez pas placer une ListView imbriquée dans une ScrollView car elle gâche le défilement - même Android Lint s'en plaint.

Alors voici ma question:

Comment connecter une ListAdapter à une LinearLayout ou quelque chose de similaire?

Je sais que cette solution ne s'adapte pas à beaucoup d'éléments, mais mes listes étant très courtes (<10 éléments), la réutilisation des vues n'est pas vraiment nécessaire. En ce qui concerne les performances, je peux vivre en plaçant toutes les vues directement dans LinearLayout.

Une solution que j'ai proposée serait de placer ma structure d'activité existante dans la section headerView de la ListView. Mais cela donne l'impression d'abuser de ce mécanisme, alors je cherche une solution plus propre.

Des idées?

UPDATE: Afin d'inspirer la bonne direction, j'ajoute un exemple de présentation pour montrer mon problème:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
              Android:id="@+id/news_detail_layout"
              Android:layout_width="fill_parent"
              Android:layout_height="fill_parent"
              Android:orientation="vertical"
              Android:visibility="visible">


    <ScrollView
            Android:layout_width="fill_parent"
            Android:layout_height="fill_parent"
            Android:background="#FFF"
            >

        <LinearLayout
                Android:layout_width="fill_parent"
                Android:layout_height="fill_parent"
                Android:orientation="vertical"
                Android:paddingLeft="@dimen/news_detail_layout_side_padding"
                Android:paddingRight="@dimen/news_detail_layout_side_padding"
                Android:paddingTop="@dimen/news_detail_layout_vertical_padding"
                Android:paddingBottom="@dimen/news_detail_layout_vertical_padding"
                >

            <TextView
                    Android:id="@+id/news_detail_date"
                    Android:layout_height="wrap_content"
                    Android:layout_width="fill_parent"
                    Android:gravity="center_horizontal"
                    Android:text="LALALA"
                    Android:textSize="@dimen/news_detail_date_height"
                    Android:textColor="@color/font_black"
                    />

            <Gallery
                    Android:id="@+id/news_detail_image"
                    Android:layout_height="wrap_content"
                    Android:layout_width="fill_parent"
                    Android:paddingTop="5dip"
                    Android:paddingBottom="5dip"
                    />

            <TextView
                    Android:id="@+id/news_detail_headline"
                    Android:layout_height="wrap_content"
                    Android:layout_width="fill_parent"
                    Android:gravity="center_horizontal"
                    Android:text="Some awesome headline"
                    Android:textSize="@dimen/news_detail_headline_height"
                    Android:textColor="@color/font_black"
                    Android:paddingTop="@dimen/news_detail_headline_paddingTop"
                    Android:paddingBottom="@dimen/news_detail_headline_paddingBottom"
                    />

            <TextView
                    Android:id="@+id/news_detail_content"
                    Android:layout_height="wrap_content"
                    Android:layout_width="fill_parent"
                    Android:text="Here comes a lot of text so the scrollview is really needed."
                    Android:textSize="@dimen/news_detail_content_height"
                    Android:textColor="@color/font_black"
                    />

            <!---
                HERE I NEED THE LIST OF ITEMS PROVIDED BY THE EXISTING ADAPTER. 
                They should be positioned at the end of the content, so making the scrollview smaller is not an option.
            ---->                        

        </LinearLayout>
    </ScrollView>
</LinearLayout>

UPDATE 2 J'ai changé le titre pour le rendre plus facile à comprendre (j'ai eu un vote négatif, doh!).

91
Stefan Hoth

Vous devriez probablement simplement ajouter manuellement vos éléments à LinearLayout:

LinearLayout layout = ... // Your linear layout.
ListAdapter adapter = ... // Your adapter.

final int adapterCount = adapter.getCount();

for (int i = 0; i < adapterCount; i++) {
  View item = adapter.getView(i, null, null);
  layout.addView(item);
}

EDIT: J'ai rejeté cette approche lorsque j'avais besoin d'afficher environ 200 éléments de liste non triviaux, c'est très lent - Nexus 4 a eu besoin d'environ 2 secondes pour afficher ma "liste", ce qui était inacceptable. Alors je me suis tourné vers l'approche de Flo avec des en-têtes. Cela fonctionne beaucoup plus rapidement car les vues de liste sont créées à la demande lorsque l'utilisateur fait défiler l'écran, et non au moment de la création de la vue.

Résumé: L'ajout manuel de vues à la mise en page est plus facile à coder (donc potentiellement moins de pièces mobiles et de bugs), mais souffre de problèmes de performances, donc si vous avez 50 vues ou plus, je vous conseille d'utiliser l'approche en-tête .

Exemple. Fondamentalement, la présentation de l'activité (ou du fragment) se transforme en quelque chose comme ceci (plus besoin de ScrollView):

<ListView xmlns:Android="http://schemas.Android.com/apk/res/Android"
  Android:id="@+id/my_top_layout"
  Android:layout_width="fill_parent"
  Android:layout_height="fill_parent"/>

Ensuite, dans onCreateView() (je vais utiliser un exemple avec un fragment), vous devez ajouter une vue d'en-tête, puis définir un adaptateur (je suppose que l'ID de la ressource d'en-tête est header_layout):

ListView listView = (ListView) inflater.inflate(R.layout.my_top_layout, container, false);
View header = inflater.inflate(R.layout.header_layout, null);
// Initialize your header here.
listView.addHeaderView(header, null, false);

BaseAdapter adapter = // ... Initialize your adapter.
listView.setAdapter(adapter);

// Just as a bonus - if you want to do something with your list items:
view.setOnItemClickListener(new AdapterView.OnItemClickListener() {
  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    // You can just use listView instead of parent casted to ListView.
    if (position >= ((ListView) parent).getHeaderViewsCount()) {
      // Note the usage of getItemAtPosition() instead of adapter's getItem() because
      // the latter does not take into account the header (which has position 0).
      Object obj = parent.getItemAtPosition(position);
      // Do something with your object.
    }
  }
});
93
smok

Je voudrais coller avec la solution de vue en-tête. Il n'y a rien de mal à cela. Pour le moment, je réalise une activité en utilisant exactement la même approche. 

De toute évidence, la "partie d'élément" est plus dynamique que statique (nombre d'éléments variable par rapport au nombre d'éléments fixes, etc.), sinon vous ne songez pas à utiliser un adaptateur. Donc, lorsque vous avez besoin d'un adaptateur, utilisez ListView.

Implémenter une solution qui renseigne LinearLayout à partir d'un adaptateur n'est rien d'autre que la construction d'un ListView avec une mise en page personnalisée. 

Juste mes 2 cents. 

6
Flo

Définissez votre vue sur main.xml onCreate, puis décompressez à partir de row.xml

main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="fill_parent"
    Android:layout_height="450dp" >

        <ListView
            Android:id="@+id/mainListView"
            Android:layout_width="fill_parent"
            Android:layout_height="fill_parent"
            Android:layout_above="@+id/size"
            Android:layout_below="@+id/editText1"
            Android:gravity="fill_vertical|fill_horizontal"
            Android:horizontalSpacing="15dp"
            Android:isScrollContainer="true"
            Android:numColumns="1"
            Android:padding="5dp"
            Android:scrollbars="vertical"
            Android:smoothScrollbar="true"
            Android:stretchMode="columnWidth" >

</ListView>

    <TextView
        Android:id="@+id/size"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_alignParentBottom="true"
        Android:layout_alignParentLeft="true"
        Android:layout_alignParentRight="true"
        Android:background="#ff444444"
        Android:gravity="center"
        Android:text="TextView"
        Android:textColor="#D3D3D3"
        Android:textStyle="italic" />

    </EditText>

</RelativeLayout> 

row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:Android="http://schemas.Android.com/apk/res/Android"
  Android:layout_width="fill_parent"
  Android:layout_height="fill_parent" 
  Android:paddingTop="3dp">

<TextView
  Android:id="@+id/rowTextView"
  Android:layout_width="0dip"
  Android:layout_height="41dp"
  Android:layout_margin="4dp"
  Android:layout_weight="2.83"
  Android:ellipsize="end"
  Android:gravity="center_vertical"
  Android:lines="1"
  Android:text="John Doe"
  Android:textColor="@color/color_white"
  Android:textSize="23dp" >
</TextView>

</LinearLayout>
0
fasheikh

J'utilise le code suivant qui réplique les fonctionnalités de l'adaptateur avec ViewGroup et TabLayout. La bonne chose à propos de cela est que si vous modifiez votre liste et que vous vous reliez à nouveau, cela n'affectera que les éléments modifiés:

Usage:

val list = mutableListOf<Person>()
layout.bindChildren(list, { it.personId }, { bindView(it) }, {d, t ->bindView(d, t)})
list.removeAt(0)
list+=newPerson
layout.bindChildren(list, { it.personId }, { bindView(it) }, {d, t ->bindView(d, t)})

Pour ViewGroups:

fun <Item, Key> ViewGroup.bindChildren(items: List<Item>, id: (Item) -> Key, view: (Item) -> View, bind: (Item, View) -> Unit) {
    val old = children.map { it.tag as Key }.toList().filter { it != null }
    val new = items.map(id)

    val add = new - old
    val remove = old - new
    val keep = new.intersect(old)

    val tagToChildren = children.associateBy { it.tag as Key }
    val idToItem = items.associateBy(id)

    remove.forEach { tagToChildren[it].let { removeView(it) } }
    keep.forEach { bind(idToItem[it]!!, tagToChildren[it]!!) }
    add.forEach { id -> view(idToItem[id]!!).also { it.tag = id }.also { addView(it, items.indexOf(idToItem[id])) } }
}

Pour TabLayout j'ai ceci:

fun <Item, Key> TabLayout.bindTabs(items: List<Item>, toKey: (Item) -> Key, tab: (Item) -> TabLayout.Tab, bind: (Item, TabLayout.Tab) -> Unit) {
    val old = (0 until tabCount).map { getTabAt(it)?.tag as Key }
    val new = items.map(toKey)

    val add = new - old
    val remove = old - new
    val keep = new.intersect(old)

    val tagToChildren = (0 until tabCount).map { getTabAt(it) }.associateBy { it?.tag as Key }
    val idToItem = items.associateBy(toKey)

    remove.forEach { tagToChildren[it].let { removeTab(it) } }
    keep.forEach { bind(idToItem[it]!!, tagToChildren[it]!!) }
    add.forEach { key -> tab(idToItem[key]!!).also { it.tag = key }.also { addTab(it, items.indexOf(idToItem[key])) } }
}
0
M-WaJeEh