Использование 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])) } }
}
M-WaJeEh
источник