Sử dụng ListAdapter để lấp đầy LinearLayout bên trong bố cục ScrollView


95

Tôi đang đối mặt với một vấn đề rất phổ biến: Tôi đã sắp xếp một hoạt động và bây giờ hóa ra nó sẽ hiển thị một vài mục trong này ScrollView. Cách thông thường để làm điều đó là sử dụng cái hiện có ListAdapter, kết nối nó với a ListViewBOOM, tôi sẽ có danh sách các mục của mình.

NHƯNG Bạn không nên đặt một cái lồng ListViewvào một cái ScrollViewvì nó làm vướng quá trình cuộn - ngay cả Android Lint cũng phàn nàn về điều đó.

Vì vậy, đây là câu hỏi của tôi:

Làm cách nào để kết nối a ListAdaptervới một LinearLayouthoặc thứ gì đó tương tự?

Tôi biết giải pháp này sẽ không chia tỷ lệ cho nhiều mục nhưng danh sách của tôi rất ngắn (<10 mục) nên việc sử dụng lại các lượt xem là không thực sự cần thiết. Hiệu suất khôn ngoan Tôi có thể sống với việc đặt tất cả các chế độ xem trực tiếp vào LinearLayout.

Một giải pháp mà tôi đã đưa ra là đặt bố cục hoạt động hiện có của tôi trong phần headerView của ListView. Nhưng điều này giống như lạm dụng cơ chế này, vì vậy tôi đang tìm kiếm một giải pháp sạch hơn.

Ý tưởng?

CẬP NHẬT: Để tạo cảm hứng cho hướng đi đúng, tôi thêm một bố cục mẫu để hiển thị vấn đề của mình:

<?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>

CẬP NHẬT 2 Tôi đã thay đổi tiêu đề để dễ hiểu hơn (nhận được phản đối, doh!).


1
Bạn đã bao giờ tìm thấy một giải pháp sạch đẹp cho vấn đề này? Tôi thấy Google sử dụng nó trong cửa hàng chơi của họ để đánh giá. Bất cứ ai biết cách họ làm điều đó?
Zapnologica

Câu trả lời:


96

Bạn có thể chỉ nên thêm thủ công các mục của mình vào 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);
}

CHỈNH SỬA : Tôi đã từ chối cách làm này khi tôi cần hiển thị khoảng 200 mục danh sách không tầm thường, nó rất chậm - Nexus 4 cần khoảng 2 giây để hiển thị "danh sách" của tôi, điều đó không thể chấp nhận được. Vì vậy, tôi đã chuyển sang cách tiếp cận của Flo với tiêu đề. Nó hoạt động nhanh hơn nhiều vì chế độ xem danh sách được tạo theo yêu cầu khi người dùng cuộn chứ không phải tại thời điểm chế độ xem được tạo.

Tiếp tục: Việc bổ sung thủ công các chế độ xem vào bố cục dễ viết mã hơn (do đó có khả năng ít các bộ phận chuyển động và lỗi hơn), nhưng gặp phải các vấn đề về hiệu suất, vì vậy nếu bạn có từ 50 chế độ xem trở lên, tôi khuyên bạn nên sử dụng cách tiếp cận tiêu đề.

Thí dụ. Về cơ bản, bố cục hoạt động (hoặc phân đoạn) chuyển đổi thành một cái gì đó như thế này (không cần ScrollView nữa):

<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"/>

Sau đó, trong onCreateView()(tôi sẽ sử dụng một ví dụ với một phân đoạn), bạn cần thêm chế độ xem tiêu đề và sau đó đặt bộ điều hợp (tôi giả sử ID tài nguyên tiêu đề là 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.
    }
  }
});

Vâng, đó là ý tưởng thứ hai tôi có nhưng tôi đã không chắc chắn về ListAdapterListViewlàm được nhiều kỳ diệu đằng sau màn hình mà tôi thì không làm bằng tay.
Stefan Hoth

Tất nhiên ListView thực hiện một số phép thuật, ít nhất là phân chia giữa các phần tử, bạn sẽ phải thêm chúng theo cách thủ công, tôi đoán vậy. ListAdapter (nếu nó được triển khai đúng cách) sẽ không thực hiện bất kỳ phép thuật nào nữa.
Hút thuốc lá

2
Đây là một phiếu bầu thấp cho tôi. Vấn đề bộ nhớ có ai không? Có một lý do chúng tôi sử dụng bộ điều hợp ...
Kevin Parker

1
Có ai khác gặp vấn đề với việc cập nhật các chế độ xem bằng notifyDataSetChangedphương pháp của bộ điều hợp không? Điều này hoạt động tốt trên một bộ điều hợp khác mà tôi đã kết nối với a ListView, nhưng có vẻ như nó không hoạt động với bộ điều hợp tôi đã kết nối với a LinearLayout.
đùaefe

2
đây là một trong những lý do tôi ghét Android. Tôi không hiểu tại sao bạn phải triển khai các giải pháp phức tạp hoặc gặp phải các vấn đề về hiệu suất.
IARI

6

Tôi sẽ gắn bó với giải pháp xem tiêu đề. Không có gì sai với điều đó cả. Hiện tại, tôi đang triển khai một hoạt động bằng cách sử dụng cùng một cách tiếp cận.

Rõ ràng là "phần vật phẩm" động hơn là tĩnh (thay đổi số lượng vật phẩm so với số lượng vật phẩm sửa chữa, v.v.) nếu không, bạn sẽ không nghĩ đến việc sử dụng bộ điều hợp. Vì vậy, khi bạn cần một bộ điều hợp thì hãy sử dụng ListView.

Cuối cùng, việc triển khai một giải pháp điền LinearLayout từ một bộ điều hợp không gì khác ngoài việc xây dựng một ListView với bố cục tùy chỉnh.

Chỉ 2 xu của tôi.


Một hạn chế của cách tiếp cận này là bạn phải di chuyển gần như toàn bộ bố cục hoạt động của mình (xem ở trên) sang một phần bố cục khác để bạn có thể tham chiếu nó dưới dạng ListHeaderView và tạo một bố cục khác chỉ bao gồm ListView. Không chắc tôi thích công việc vá lỗi này.
Stefan Hoth

1
Vâng, tôi biết ý bạn là gì, trước khi bắt đầu sử dụng phương pháp này, tôi cũng không thích ý tưởng bố trí hoạt động của mình được tách thành nhiều tệp. Nhưng khi tôi thấy rằng bố cục tổng thể trông như tôi mong đợi, tôi không bận tâm về việc tách biệt vì nó chỉ được tách thành hai tệp. Một điều bạn phải biết, ListView không được có dạng xem trống vì điều này sẽ ẩn tiêu đề danh sách của bạn khi không có mục danh sách.
Flo

3
Ngoài ra, nếu bạn muốn có nhiều danh sách trên một trang, điều này không thực sự hiệu quả. Có ai có ví dụ về chế độ xem bộ điều hợp tùy chỉnh xuất các mục thành một danh sách "phẳng", tức là một danh sách không cuộn.
murtuza

0

Đặt chế độ xem của bạn thành main.xml onCreate, sau đó tăng từ 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>

Tôi nghĩ rằng bạn không hiểu câu hỏi. Anh ấy không muốn điền danh sách bằng LinearLayouts.
Flo

"Làm cách nào để kết nối ListAdapter với LinearLayout hoặc thứ gì đó tương tự?" Chỉ cần trả lời những gì các OP hỏi
fasheikh

2
Không, anh ấy không muốn sử dụng ListView. Anh ta chỉ muốn sử dụng một bộ điều hợp và sử dụng nó để điền vào một LinearLayout với các mục nhập.
Flo

Cảm ơn câu trả lời của bạn nhưng @Flo là chính xác. Tôi không thể sử dụng ListView vì bố cục bên ngoài đã là ScrollView. Vui lòng kiểm tra văn bản của tôi và bố cục mẫu.
Stefan Hoth

tại sao cần có scrollView đó?
fasheikh

0

Tôi sử dụng mã sau đây sao chép chức năng bộ điều hợp với ViewGroupTabLayout. Điều tốt về điều này là nếu bạn thay đổi danh sách của mình và liên kết lại, nó sẽ chỉ ảnh hưởng đến các mục đã thay đổi:

Sử dụng:

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

Đối với 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])) } }
}

TabLayouttôi có cái này:

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])) } }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.