【问题标题】:Using a ListAdapter to fill a LinearLayout inside a ScrollView layout使用 ListAdapter 在 ScrollView 布局中填充 LinearLayout
【发布时间】:2018-05-19 07:41:10
【问题描述】:

我面临一个非常常见的问题: 我布置了一个活动,现在它应该在这个ScrollView 中显示一些项目。正常的做法是使用现有的ListAdapter,将其连接到ListViewBOOM我会有我的项目列表。

但是您不应该将嵌套的 ListView 放在 ScrollView 中,因为它会破坏滚动 - 甚至 Android Lint 都会抱怨它。

这是我的问题:

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

我知道这个解决方案不会针对很多项目进行扩展,但我的列表非常短(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 我更改了标题以使其更易于理解(投反对票,doh!)。

【问题讨论】:

  • 你有没有找到一个很好的干净的解决方案来解决这个问题?我看到谷歌在他们的游戏商店中使用它来进行评论。有人知道他们是怎么做到的吗?

标签: android android-layout android-arrayadapter


【解决方案1】:

您可能应该手动将您的项目添加到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);
}

编辑:当我需要显示大约 200 个重要的列表项时,我拒绝了这种方法,它非常慢 - Nexus 4 需要大约 2 秒来显示我的“列表”,这是不可接受的.所以我转向了弗洛的带标题的方法。它工作得更快,因为列表视图是在用户滚动时按需创建的,而不是在创建视图时。

简历: 在布局中手动添加视图更容易编码(因此可能会减少移动部件和错误),但会遇到性能问题,因此如果您有 50 个或更多视图,我建议使用标题方法。

示例。基本上活动(或片段)布局转换为这样的东西(不再需要 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"/>

然后在onCreateView()(我将使用带有片段的示例)您需要添加一个标题视图,然后设置一个适配器(我假设标题资源ID是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.
    }
  }
});

【讨论】:

  • 是的,这是我的第二个想法,但我不确定 ListAdapterListView 在屏幕后面有更多的魔力,而我不会手动操作。跨度>
  • 当然 ListView 有一些魔力,至少元素之间的分隔符,我猜你必须手动添加它们。 ListAdapter(如果实现正确的话)不应该再做任何魔术了。
  • 这是对我的反对票。内存问题有人吗?我们使用适配器是有原因的......
  • 是否有其他人在使用适配器的notifyDataSetChanged 方法更新视图时遇到问题?这在我连接到ListView 的另一个适配器上运行良好,但它似乎不适用于我连接到LinearLayout 的适配器。
  • 这是我讨厌安卓的原因之一。我不明白为什么您必须实施复杂的解决方案,或者遇到性能问题。
【解决方案2】:

我会坚持使用标题视图解决方案。它没有任何问题。目前,我使用完全相同的方法实施一项活动。

显然,“项目部分”比静态更动态(改变项目数与固定项目数等),否则您根本不会考虑使用适配器。因此,当您需要适配器时,请使用 ListView。

实施从适配器填充 LinearLayout 的解决方案最终只不过是构建具有自定义布局的 ListView。

只有我的 2 美分。

【讨论】:

  • 这种方法的一个缺点是您必须将几乎整个活动布局(见上文)移动到另一块布局中,以便您可以将其作为 ListHeaderView 引用并创建另一个布局,其中仅包含 @ 987654321@。不确定我喜欢这个补丁工作。
  • 是的,我知道你的意思,在我开始使用这种方法之前,我也不喜欢将我的活动布局分成多个文件的想法。但是当我看到整体布局看起来像我预期的那样时,我并不介意分离,因为它只分成两个文件。您必须注意的一件事,ListView 不能有一个空视图,因为当没有列表项时,这将隐藏您的列表标题。
  • 另外,如果你想在一个页面上有多个列表,这实际上是行不通的。有没有人有一个自定义适配器视图的示例,该视图将项目输出到“平面”列表中,即不滚动的列表。
【解决方案3】:

将视图设置为 main.xml onCreate,然后从 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> 

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

【讨论】:

  • 我想你错过了理解这个问题。他不想用 LinearLayouts 填充列表。
  • "如何将 ListAdapter 连接到 LinearLayout 或类似的东西?"只是回答 OP 的问题
  • 不,他不想使用 ListView。他只想使用适配器并使用它来填充带有条目的 LinearLayout。
  • 感谢您的回答,但@Flo 是正确的。我不能使用 ListView,因为外部布局已经是 ScrollView。请检查我的文本和示例布局。
  • 为什么需要 scrollView?
【解决方案4】:

我使用以下代码复制适配器功能与ViewGroupTabLayout。这样做的好处是,如果您更改列表并再次绑定,它只会影响更改的项目:

用法:

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

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

对于TabLayout,我有这个:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-06
    • 2010-11-15
    • 1970-01-01
    相关资源
    最近更新 更多