使用 ListAdapter 在 ScrollView 布局中填充 LinearLayout

我面临着一个非常普遍的问题: 我设计了一个活动,现在它应该在这个 ScrollView中显示一些项目。通常的做法是使用现有的 ListAdapter,连接到一个 ListView我会有我的项目列表。

但是 你不应该把一个嵌套的 ListView放在一个 ScrollView中,因为它会扰乱滚动——甚至 Android Lint 也抱怨它。

所以我的问题是:

如何将一个 ListAdapter连接到一个 LinearLayout或类似的东西?

我知道这个解决方案不会扩展到很多项目,但是我的列表非常短(< 10个项目) ,因此并不真正需要重用视图。在性能方面,我可以将所有视图直接放入 LinearLayout

我想到的一个解决方案是将我现有的活动布局放在 ListView的 headerView 部分。但这感觉像是滥用这个机制,所以我在寻找一个更干净的解决方案。

有什么想法吗?

更新: 为了激发正确的方向,我添加了一个样本布局来展示我的问题:

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

更新2 我改变了标题,使它更容易理解(得到否决票,哦!)。

46417 次浏览

Set your view to main.xml onCreate, then inflate from 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>

You probably should just manually add your items to 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: I rejected this approach when I needed to display about 200 non-trivial list items, it is very slow - Nexus 4 needed about 2 seconds to display my "list", that was unacceptable. So I turned to Flo's approach with headers. It works much faster because list views are created on demand when user scrolls, not at the time the view is created.

Resume: The manual addition of views to layout is easier to code (thus potentially less moving parts and bugs), but suffers from performance problems, so if you have like 50 views or more, I advise to use the header approach.

Example. Basically the activity (or fragment) layout transforms to something like this (no ScrollView needed anymore):

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

Then in onCreateView() (I'll use an example with a fragment) you need to add a header view and then set an adapter (I assume the header resource ID is 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.
}
}
});

I would stick with the header view solution. There's nothing wrong with it. At the moment I implementing an activity using the exact same approach.

Obviously the "item part" is more dynamically than static (varying item count vs. fix item count etc.) otherwise you won't think about using an adapter at all. So when you need an adapter then use the ListView.

Implementing a solution which populates a LinearLayout from an adapter is in the end nothing else than building a ListView with a custom layout.

Just my 2 cents.

I use following code which replicate adapter functionality with ViewGroup and TabLayout. Good thing about this is that if you change your list and bind again, it will only affect changed items:

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

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

For TabLayout I have this:

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