Używanie ListAdapter do wypełnienia LinearLayout wewnątrz układu ScrollView

95

Mam do czynienia z bardzo częstym problemem: ułożyłem działanie i teraz okazuje się, że powinno ono zawierać kilka elementów ScrollView. Normalnym sposobem na to byłoby użycie istniejącego ListAdapter, podłączenie go do a ListViewi BOOM miałbym moją listę pozycji.

ALE Nie powinieneś umieszczać zagnieżdżonego ListVieww a, ScrollViewponieważ psuje przewijanie - nawet Android Lint narzeka na to.

Oto moje pytanie:

Jak podłączyć a ListAdapterdo LinearLayoutlub coś podobnego?

Wiem, że to rozwiązanie nie będzie skalowane dla wielu pozycji, ale moje listy są bardzo krótkie (<10 pozycji), więc ponowne wykorzystanie widoków nie jest tak naprawdę potrzebne. Jeśli chodzi o wydajność, mogę żyć z umieszczaniem wszystkich widoków bezpośrednio w LinearLayout.

Jednym z rozwiązań, które wymyśliłem, byłoby umieszczenie mojego istniejącego układu działań w sekcji headerView pliku ListView. Ale to wygląda na nadużycie tego mechanizmu, więc szukam czystszego rozwiązania.

Pomysły?

AKTUALIZACJA: Aby zainspirować właściwy kierunek, dodaję przykładowy układ, aby pokazać mój problem:

<?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 Zmieniłem nagłówek, aby był łatwiejszy do zrozumienia (dostałem negatywną opinię, doh!).

Stefan Hoth
źródło
1
Czy kiedykolwiek znalazłeś ładne, czyste rozwiązanie tego problemu? Widzę, że Google używa go w swoim sklepie z zabawkami do recenzji. Czy ktoś wie, jak oni to robią?
Zapnologica

Odpowiedzi:

96

Prawdopodobnie powinieneś po prostu ręcznie dodać swoje przedmioty do 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);
}

EDYCJA : Odrzuciłem to podejście, gdy musiałem wyświetlić około 200 nietrywialnych elementów listy, jest bardzo powolne - Nexus 4 potrzebował około 2 sekund na wyświetlenie mojej „listy”, co było nie do przyjęcia. Więc zwróciłem się do podejścia Flo z nagłówkami. Działa znacznie szybciej, ponieważ widoki list są tworzone na żądanie podczas przewijania przez użytkownika, a nie w momencie tworzenia widoku.

Wznów: Ręczne dodawanie widoków do układu jest łatwiejsze do zakodowania (stąd potencjalnie mniej ruchomych części i błędów), ale ma problemy z wydajnością, więc jeśli masz 50 lub więcej widoków, radzę użyć podejścia nagłówkowego.

Przykład. Zasadniczo układ aktywności (lub fragmentu) przekształca się w coś takiego (nie jest już potrzebny 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"/>

Następnie w onCreateView()(użyję przykładu z fragmentem) musisz dodać widok nagłówka, a następnie ustawić adapter (zakładam, że identyfikator zasobu nagłówka to 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.
    }
  }
});
smok
źródło
Tak, to był drugi pomysł, jaki miałem, ale nie byłem tego pewien ListAdapteri ListViewrobi więcej magii za ekranami, których potem nie robię ręcznie.
Stefan Hoth,
Oczywiście ListView robi trochę magii, przynajmniej dzieli elementy między elementami, myślę, że musiałbyś dodać je ręcznie. ListAdapter (jeśli jest poprawnie zaimplementowany) nie powinien już robić magii.
smok
2
To jest dla mnie głos negatywny. Czy ktoś ma problemy z pamięcią? Nie bez powodu używamy adapterów ...
Kevin Parker
1
Czy ktoś jeszcze ma problemy z aktualizacją widoków metodą adaptera notifyDataSetChanged? Działa to dobrze z innym adapterem, który podłączyłem do a ListView, ale wydaje się , że nie działa z tym, który podłączyłem do LinearLayout.
żart
2
to jeden z powodów, dla których nienawidzę Androida. Nie rozumiem, dlaczego musisz wdrażać skomplikowane rozwiązania lub cierpieć z powodu problemów z wydajnością.
IARI
6

Trzymałbym się rozwiązania z widokiem nagłówka. Nie ma w tym nic złego. W tej chwili realizuję działanie przy użyciu dokładnie tego samego podejścia.

Oczywiście „część pozycji” jest bardziej dynamiczna niż statyczna (zmienna liczba pozycji w porównaniu z liczbą naprawionych pozycji itp.), W przeciwnym razie w ogóle nie myślisz o używaniu adaptera. Więc kiedy potrzebujesz adaptera, użyj ListView.

Implementacja rozwiązania, które wypełnia LinearLayout z adaptera, to w końcu nic innego jak zbudowanie ListView z niestandardowym układem.

Tylko moje 2 centy.

Flo
źródło
Jedną z wad tego podejścia jest to, że musisz przenieść prawie cały układ aktywności (patrz powyżej) do innego elementu układu, aby można było odwołać się do niego jako ListHeaderView i utworzyć inny układ zawierający tylko ListView. Nie jestem pewien, czy podoba mi się ta łatka.
Stefan Hoth,
1
Tak, wiem, co masz na myśli, zanim zacząłem używać tego podejścia, nie podobał mi się również pomysł, aby mój układ działań został podzielony na wiele plików. Ale kiedy zobaczyłem, że ogólny układ wygląda tak, jak się spodziewałem, nie przejmowałem się separacją, ponieważ jest on podzielony tylko na dwa pliki. Jedna rzecz, o której musisz wiedzieć, ListView nie może mieć pustego widoku, ponieważ spowoduje to ukrycie nagłówka listy, gdy nie ma żadnych elementów listy.
Flo
3
Ponadto, jeśli chcesz mieć wiele list na stronie, to tak naprawdę nie działa. Czy ktoś ma przykład niestandardowego widoku adaptera, który wyprowadza elementy na „płaską” listę, tj. Taką, która się nie przewija.
murtuza 12.07.14
0

Ustaw widok na main.xml onCreate, a następnie napompuj z 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>
fasheikh
źródło
Myślę, że tęskniłeś, zrozumiałeś pytanie. Nie chce zapełniać listy układami LinearLayouts.
Flo
„Jak połączyć ListAdapter z LinearLayout lub czymś podobnym?” Odpowiadam tylko o to, o co prosił OP
fasheikh
2
Nie, on nie chce używać ListView. Chce tylko użyć adaptera i użyć go do wypełnienia LinearLayout wpisami.
Flo
Dziękuję za odpowiedź, ale @Flo jest poprawne. Nie mogę użyć ListView, ponieważ układ zewnętrzny jest już ScrollView. Proszę sprawdzić mój tekst i przykładowy układ.
Stefan Hoth,
dlaczego potrzebny jest scrollView?
fasheikh
0

Używam następującego kodu, który replikuje funkcjonalność adaptera z ViewGroupi TabLayout. Dobrą rzeczą jest to, że jeśli zmienisz listę i ponownie utworzysz powiązanie, wpłynie to tylko na zmienione elementy:

Stosowanie:

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

Dla 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])) } }
}

Dla TabLayoutmam to:

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])) } }
}
M-WaJeEh
źródło