Menggunakan ListAdapter untuk mengisi LinearLayout di dalam tata letak ScrollView

95

Saya menghadapi masalah yang sangat umum: Saya menyusun sebuah aktivitas dan sekarang ternyata aktivitas tersebut harus menampilkan beberapa item di dalamnya ScrollView. Cara normal untuk melakukannya adalah dengan menggunakan yang ada ListAdapter, hubungkan ke a ListViewdan BOOM Saya akan memiliki daftar item saya.

TETAPI Anda tidak boleh menempatkan bersarang ListViewdi a ScrollViewkarena mengacaukan pengguliran - bahkan Android Lint mengeluhkannya.

Jadi, inilah pertanyaan saya:

Bagaimana cara menghubungkan ListAdapterke a LinearLayoutatau yang serupa?

Saya tahu solusi ini tidak akan diskalakan untuk banyak item tetapi daftar saya sangat pendek (<10 item) sehingga penggunaan kembali tampilan tidak terlalu diperlukan. Dari segi kinerja, saya bisa hidup dengan menempatkan semua tampilan langsung ke LinearLayout.

Satu solusi yang saya dapatkan adalah menempatkan layout aktivitas saya yang ada di bagian headerView dari ListView. Tapi ini terasa seperti menyalahgunakan mekanisme ini, jadi saya mencari solusi yang lebih bersih.

Ide ide?

UPDATE: Untuk menginspirasi arah yang benar, saya menambahkan contoh tata letak untuk menunjukkan masalah saya:

<?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 Saya mengubah headline agar lebih mudah dipahami (mendapat downvote, doh!).

Stefan Hoth
sumber
1
Apakah Anda pernah menemukan solusi bersih yang bagus untuk masalah ini? Saya melihat Google menggunakannya di toko bermain mereka untuk ulasan. Ada yang tahu bagaimana mereka melakukannya?
Zapnologica

Jawaban:

96

Anda mungkin harus menambahkan item Anda secara manual ke 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 : Saya menolak pendekatan ini ketika saya perlu menampilkan sekitar 200 item daftar non-sepele, ini sangat lambat - Nexus 4 membutuhkan sekitar 2 detik untuk menampilkan "daftar" saya, itu tidak dapat diterima. Jadi saya beralih ke pendekatan Flo dengan header. Ini bekerja lebih cepat karena tampilan daftar dibuat sesuai permintaan saat pengguna menggulir, bukan pada saat tampilan dibuat.

Lanjutkan: Penambahan tampilan manual ke tata letak lebih mudah untuk dikodekan (sehingga bagian dan bug yang berpotensi kurang bergerak), tetapi mengalami masalah kinerja, jadi jika Anda memiliki 50 tampilan atau lebih, saya menyarankan untuk menggunakan pendekatan tajuk.

Contoh. Pada dasarnya tata letak aktivitas (atau fragmen) berubah menjadi seperti ini (tidak diperlukan ScrollView lagi):

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

Kemudian di onCreateView()(Saya akan menggunakan contoh dengan fragmen) Anda perlu menambahkan tampilan header dan kemudian menyetel adaptor (saya menganggap ID sumber daya header adalah 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.
    }
  }
});
asap
sumber
Ya, itu adalah ide kedua yang saya miliki tetapi saya tidak yakin ListAdapterdan ListViewmelakukan lebih banyak keajaiban di balik layar yang kemudian tidak saya lakukan secara manual.
Stefan Hoth
Tentu saja ListView melakukan keajaiban, setidaknya pemisah antar elemen, Anda harus menambahkannya secara manual, saya kira. ListAdapter (jika diimplementasikan dengan benar) seharusnya tidak melakukan sihir lagi.
merokok
2
Ini adalah suara buruk bagi saya. Masalah memori siapa? Ada alasan kami menggunakan adaptor ...
Kevin Parker
1
Apakah ada orang lain yang mengalami masalah dengan memperbarui tampilan menggunakan metode adaptor notifyDataSetChanged? Ini berfungsi dengan baik pada adaptor lain yang telah saya sambungkan ListView, tetapi tampaknya tidak berfungsi dengan adaptor yang telah saya sambungkan LinearLayout.
jokeefe
2
ini adalah salah satu alasan saya membenci android. Saya tidak mengerti mengapa Anda harus menerapkan solusi yang rumit, atau mengalami masalah kinerja.
IARI
6

Saya akan tetap menggunakan solusi tampilan header. Tidak ada yang salah dengan itu. Saat ini saya melaksanakan suatu kegiatan dengan menggunakan pendekatan yang sama persis.

Jelas sekali, "bagian item" lebih dinamis daripada statis (memvariasikan jumlah item vs. memperbaiki jumlah item, dll.) Jika tidak, Anda tidak akan berpikir untuk menggunakan adaptor sama sekali. Jadi, ketika Anda membutuhkan adaptor, gunakan ListView.

Mengimplementasikan solusi yang mengisi LinearLayout dari adaptor pada akhirnya tidak lain adalah membangun ListView dengan tata letak khusus.

Hanya 2 sen saya.

Flo
sumber
Satu kelemahan dari pendekatan ini adalah Anda harus memindahkan hampir seluruh layout aktivitas Anda (lihat di atas) ke bagian layout lain sehingga Anda bisa mereferensikannya sebagai ListHeaderView dan membuat layout lain hanya dengan menyertakan ListView. Tidak yakin saya suka pekerjaan tambalan ini.
Stefan Hoth
1
Ya, saya mengerti maksud Anda, sebelum saya mulai menggunakan pendekatan ini, saya juga tidak menyukai gagasan untuk memisahkan layout aktivitas menjadi beberapa file. Tetapi ketika saya melihat bahwa tata letak keseluruhan tampak seperti yang saya harapkan, saya tidak keberatan tentang pemisahan karena hanya dipisahkan menjadi dua file. Satu hal yang harus Anda ketahui, ListView tidak boleh memiliki tampilan kosong karena ini akan menyembunyikan header daftar Anda saat tidak ada item daftar.
Flo
3
Selain itu, jika Anda ingin memiliki banyak daftar pada satu halaman, ini tidak benar-benar berfungsi. Apakah ada yang punya contoh tampilan adaptor khusus yang mengeluarkan item ke dalam daftar "datar", yaitu yang tidak menggulir.
murtuza
0

Setel tampilan Anda ke main.xml onCreate, lalu perbesar dari 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
sumber
Saya pikir Anda rindu memahami pertanyaan itu. Dia tidak ingin mengisi daftar dengan LinearLayouts.
Flo
"Bagaimana cara menghubungkan ListAdapter ke LinearLayout atau yang serupa?" Hanya menjawab apa yang diminta OP
fasheikh
2
Tidak, dia tidak ingin menggunakan ListView. Dia hanya ingin menggunakan adaptor dan menggunakannya untuk mengisi LinearLayout dengan entri.
Flo
Terima kasih atas jawaban Anda, tetapi @Flo benar. Saya tidak bisa menggunakan ListView karena tata letak luar sudah menjadi ScrollView. Silakan periksa teks saya dan contoh tata letak.
Stefan Hoth
mengapa scrollView itu dibutuhkan?
fasheikh
0

Saya menggunakan kode berikut yang mereplikasi fungsionalitas adaptor dengan ViewGroupdan TabLayout. Hal yang baik tentang ini adalah jika Anda mengubah daftar dan mengikat lagi, ini hanya akan memengaruhi item yang diubah:

Pemakaian:

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

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

Karena TabLayoutsaya punya ini:

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
sumber