Использование ListAdapter для заполнения LinearLayout внутри макета ScrollView


95

Я столкнулся с очень распространенной проблемой: я разместил действие, и теперь оказалось, что в нем должны отображаться несколько элементов ScrollView. Обычный способ сделать это - использовать существующий ListAdapter, подключить его к a ListViewи БУМ, у меня будет мой список элементов.

НО вы не должны помещать вложенный ListViewв a, ScrollViewпоскольку он портит прокрутку - даже Android Lint жалуется на это.

Итак, вот мой вопрос:

Как мне подключить ListAdapterк LinearLayoutили что-то подобное?

Я знаю, что это решение не масштабируется для многих элементов, но мои списки очень короткие (<10 элементов), поэтому повторное использование представлений на самом деле не требуется. Что касается производительности, я могу жить, помещая все представления прямо в файл LinearLayout.

Одно из решений, которое я придумал, - это разместить существующий макет активности в разделе headerView файла ListView. Но это похоже на злоупотребление этим механизмом, поэтому я ищу более чистое решение.

Идеи?

ОБНОВЛЕНИЕ: Чтобы вдохновить правильное направление, я добавляю образец макета, чтобы показать мою проблему:

<?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 Я изменил заголовок, чтобы его было легче понять (получил отрицательный голос, о!).


1
Вы когда-нибудь находили хорошее чистое решение этой проблемы? Я вижу, что Google использует его в своем магазине игр для обзоров. Кто-нибудь знает, как они это делают?
— Zapnologica

Ответы:


96

Вероятно, вам следует просто вручную добавить свои элементы в 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()(я буду использовать пример с фрагментом) вам нужно добавить представление заголовка, а затем установить адаптер (я предполагаю, что идентификатор ресурса заголовка 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.
    }
  }
});

Да, это была вторая идея, но я не был уверен в ней, ListAdapterи она ListViewтворит больше магии за экранами, чего я тогда не делаю вручную.
— Стефан Хот,

Конечно, ListView творит чудеса, по крайней мере, разделители между элементами, думаю, вам придется добавлять их вручную. ListAdapter (если он реализован правильно) больше не должен творить чудеса.
— Smok

2
Это голосование против меня. У кого-нибудь проблемы с памятью? Есть причина, по которой мы используем адаптеры ...
— Кевин Паркер

1
У кого-нибудь еще есть проблемы с обновлением просмотров с помощью notifyDataSetChangedметода адаптера ? Это отлично работает с другим адаптером, который я подключил к a ListView, но, похоже, он не работает с тем, что я подключил к LinearLayout.
— jokeefe

2
это одна из причин, по которой я ненавижу андроид. Я не понимаю, почему вы должны реализовывать сложные решения или страдать от проблем с производительностью.
— IARI

6

Я бы придерживался решения для просмотра заголовков. В этом нет ничего плохого. На данный момент я реализую деятельность, используя точно такой же подход.

Очевидно, что «элементная часть» более динамична, чем статическая (различное количество элементов или исправление количества элементов и т. Д.), Иначе вы вообще не будете думать об использовании адаптера. Поэтому, когда вам нужен адаптер, используйте ListView.

Реализация решения, которое заполняет LinearLayout из адаптера, - это не что иное, как создание ListView с настраиваемым макетом.

Только мои 2 цента.


Одним из недостатков этого подхода является то, что вам нужно переместить почти весь макет вашей деятельности (см. Выше) в другой фрагмент макета, чтобы вы могли ссылаться на него как на ListHeaderView и создать еще один макет, просто включая ListView. Не уверен, что мне нравится этот патч.
— Стефан Хот

1
Да, я понимаю, что вы имеете в виду, до того, как я начал использовать этот подход, мне также не нравилась идея разделения моей схемы действий на несколько файлов. Но когда я увидел, что общий макет выглядит так, как я ожидал, я не возражал против разделения, поскольку он разделен только на два файла. Вы должны знать одну вещь: у ListView не должно быть пустого представления, поскольку это скроет заголовок вашего списка, когда нет элементов списка.
— Flo

3
Кроме того, если вы хотите, чтобы на странице было несколько списков, это не сработает. Есть ли у кого-нибудь пример пользовательского представления адаптера, которое выводит элементы в «плоский» список, то есть тот, который не прокручивается.
— murtuza

0

Установите вид 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> 

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>

Я думаю, вы не поняли вопрос. Он не хочет заполнять список LinearLayouts.
— Flo

«Как мне подключить ListAdapter к LinearLayout или чему-то подобному?» Просто
— отвечу

2
Нет, он не хочет использовать ListView. Он только хочет использовать адаптер и использовать его для заполнения LinearLayout записями.
— Flo

Спасибо за ответ, но @Flo верен. Я не могу использовать ListView, потому что внешний макет уже является ScrollView. Пожалуйста, проверьте мой текст и образец макета.
— Стефан Хот,

зачем нужен этот scrollView?
— fasheikh

0

Я использую следующий код, который воспроизводит функциональность адаптера с помощью ViewGroupи TabLayout. Хорошая вещь в том, что если вы измените свой список и снова выполните привязку, это повлияет только на измененные элементы:

Использование:

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])) } }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.