การใช้ ListAdapter เพื่อเติม LinearLayout ภายในเค้าโครง ScrollView


95

ฉันหันหน้าไปทางปัญหาที่พบบ่อยมาก: ScrollViewฉันวางออกกิจกรรมและตอนนี้ก็จะเปิดออกก็ควรแสดงไม่กี่รายการที่อยู่ในนี้ วิธีปกติในการทำเช่นนั้นคือใช้สิ่งที่มีอยู่ListAdapterเชื่อมต่อกับListViewและบูมฉันจะมีรายการของฉัน

แต่คุณไม่ควรวางสิ่งที่ซ้อนกันListViewไว้ในScrollViewขณะที่มันเลื่อนขึ้น - แม้แต่ Android Lint ก็บ่นเกี่ยวกับเรื่องนี้

นี่คือคำถามของฉัน:

ฉันจะเชื่อมต่อListAdapterกับ a 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 ใช้มันใน Play Store เพื่อรีวิว ใครทราบว่าพวกเขาทำอย่างไร
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 วินาทีในการแสดง "รายการ" ซึ่งเป็นสิ่งที่ยอมรับไม่ได้ ดังนั้นฉันจึงหันไปหาแนวทางของ Flo ด้วยส่วนหัว ทำงานได้เร็วขึ้นมากเนื่องจากมุมมองรายการถูกสร้างขึ้นตามความต้องการเมื่อผู้ใช้เลื่อนไม่ใช่ในเวลาที่สร้างมุมมอง

ดำเนินการต่อ:การเพิ่มมุมมองในการจัดวางด้วยตนเองนั้นง่ายกว่าในการเขียนโค้ด (ซึ่งอาจทำให้ชิ้นส่วนและข้อบกพร่องเคลื่อนไหวน้อยลง) แต่มีปัญหาด้านประสิทธิภาพดังนั้นหากคุณมีมุมมอง 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.
    }
  }
});

ใช่นั่นเป็นความคิดที่สองที่ฉันมี แต่ฉันไม่แน่ใจในเรื่องนี้ListAdapterและความListViewมหัศจรรย์อื่น ๆ ที่อยู่เบื้องหลังหน้าจอซึ่งฉันไม่ได้ทำด้วยตนเอง
Stefan Hoth

แน่นอน ListView สร้างเวทมนตร์บางอย่างอย่างน้อยก็แบ่งระหว่างองค์ประกอบคุณจะต้องเพิ่มด้วยตนเองฉันเดา ListAdapter (หากมีการใช้งานอย่างถูกต้อง) ไม่ควรใช้เวทมนตร์อีกต่อไป
Smok

2
นี่คือการโหวตลงสำหรับฉัน หน่วยความจำมีปัญหากับใคร? มีเหตุผลที่เราใช้อะแดปเตอร์ ...
Kevin Parker

1
มีใครมีปัญหาในการอัปเดตมุมมองโดยใช้notifyDataSetChangedวิธีการของอะแดปเตอร์หรือไม่? นี้ทำงานได้ดีบนอะแดปเตอร์ที่แตกต่างกันผมได้ติดยาเสพติดขึ้นไปแต่ก็ดูเหมือนจะไม่ได้ร่วมงานกับคนที่ฉันมีได้ติดยาเสพติดไปยังListView LinearLayout
funnyefe

2
นี่เป็นหนึ่งในเหตุผลที่ฉันเกลียด Android ฉันไม่เห็นว่าทำไมคุณต้องใช้โซลูชันที่ซับซ้อนหรือประสบปัญหาด้านประสิทธิภาพ
IARI

6

ฉันจะติดกับโซลูชันมุมมองส่วนหัว ไม่มีอะไรผิดปกติ ในขณะนี้ฉันดำเนินกิจกรรมโดยใช้แนวทางเดียวกัน

เห็นได้ชัดว่า "ชิ้นส่วนรายการ" เป็นแบบไดนามิกมากกว่าแบบคงที่ (จำนวนรายการที่แตกต่างกันเทียบกับจำนวนรายการที่แก้ไขเป็นต้น) มิฉะนั้นคุณจะไม่คิดที่จะใช้อะแดปเตอร์เลย ดังนั้นเมื่อคุณต้องการอะแดปเตอร์ให้ใช้ ListView

การใช้โซลูชันที่เติมข้อมูล LinearLayout จากอะแด็ปเตอร์นั้นไม่มีอะไรอื่นนอกจากการสร้าง ListView ด้วยเค้าโครงที่กำหนดเอง

แค่ 2 เซ็นต์ของฉัน


ข้อเสียเปรียบประการหนึ่งของวิธีนี้คือคุณต้องย้ายเค้าโครงกิจกรรมเกือบทั้งหมดของคุณ (ดูด้านบน) ไปยังส่วนอื่นของเลย์เอาต์เพื่อให้คุณสามารถอ้างอิงเป็น ListHeaderView และสร้างเลย์เอาต์อื่นรวมถึงไฟล์ListView. ไม่แน่ใจว่าชอบงานแพทช์นี้
Stefan Hoth

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 หรือสิ่งที่คล้ายกันได้อย่างไร" เพียงแค่ตอบสิ่งที่ OP ถาม
fasheikh

2
ไม่เขาไม่ต้องการใช้ ListView เขาต้องการใช้เพียงอะแด็ปเตอร์และใช้เพื่อเติมข้อมูล LinearLayout ด้วยรายการ
Flo

ขอบคุณสำหรับคำตอบ แต่ @Flo ถูกต้อง ฉันไม่สามารถใช้ ListView ได้เนื่องจากเค้าโครงด้านนอกเป็น ScrollView อยู่แล้ว โปรดตรวจสอบข้อความของฉันและเค้าโครงตัวอย่าง
Stefan Hoth

เหตุใดจึงจำเป็นต้องมี 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])) } }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.