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


95

Я стикаюся з дуже поширеною проблемою: я розпочав діяльність, і тепер виявляється, що вона повинна відображати кілька елементів у цьому ScrollView. Звичайний спосіб зробити це - використовувати існуючий ListAdapter, підключити його до ListViewі BOOM Я б мав свій список елементів.

АЛЕ ви не повинні розміщувати вкладене ListViewв, ScrollViewоскільки воно викручує прокрутку - навіть Android Lint скаржиться на це.

Тож ось моє запитання:

Як підключити a 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);
}

EDIT : Я відхилив цей підхід, коли мені потрібно було відобразити близько 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робить більше магії за екранами, що я потім не роблю вручну.
Stefan Hoth

Звичайно, ListView робить якусь магію, принаймні роздільники між елементами, вам, мабуть, доведеться додавати їх вручну. ListAdapter (якщо він реалізований правильно) більше не повинен робити магії.
smok

2
Це голос проти мене. Проблема з пам’яттю у когось? Є причина, через яку ми використовуємо адаптери ...
Кевін Паркер,

1
Хтось ще має проблеми з оновленням подань за допомогою notifyDataSetChangedметоду адаптера ? Це чудово працює на іншому адаптері, який я підключив до ListView, але, схоже, не працює з тим, до якого я підключив LinearLayout.
jokeefe

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

6

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

Очевидно, що "частина елемента" є більш динамічною, ніж статична (змінюється кількість елементів у порівнянні з фіксованою кількістю елементів тощо), інакше ви взагалі не будете думати про використання адаптера. Тому, коли вам потрібен адаптер, використовуйте ListView.

Реалізація рішення, яке заповнює LinearLayout з адаптера, врешті-решт є не чим іншим, як побудовою ListView зі спеціальним макетом.

Всього мої 2 копійки.


Одним недоліком цього підходу є те, що вам потрібно перенести майже весь макет своєї діяльності (див. Вище) в інший фрагмент макета, щоб ви могли посилатися на нього як на ListHeaderView і створювати інший макет, що включає ListView. Не впевнений, що мені подобається ця робота з виправленнями.
Stefan Hoth

1
Так, я знаю, що ви маєте на увазі, до того, як я почав використовувати цей підхід, мені також не сподобалася ідея розділити мою діяльність на кілька файлів. Але коли я побачив, що загальний макет виглядав так, як я очікував, я не заперечував проти поділу, оскільки він розділений лише на два файли. Потрібно пам’ятати про те, що ListView не повинен мати порожнього подання, оскільки це приховає заголовок вашого списку, коли елементів списку немає.
Фло

3
Крім того, якщо ви хочете мати кілька списків на сторінці, це насправді не працює. Хтось має приклад користувацького подання адаптера, який виводить елементи до "плоского" списку, тобто такого, який не прокручується.
муртуза

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.
Фло

"Як підключити ListAdapter до LinearLayout або щось подібне?" Тільки відповідаючи на запитання ОП
фашейх

2
Ні, він не хоче використовувати ListView. Він хоче лише використовувати адаптер і використовувати його для заповнення LinearLayout записами.
Фло

Дякуємо за відповідь, але @Flo правильний. Я не можу використовувати ListView, оскільки зовнішній макет уже є ScrollView. Будь ласка, перевірте мій текст та зразок макета.
Стефан Хот,

навіщо потрібен цей scrollView?
фашейх

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.