Làm cách nào tôi có thể đặt ListView vào ScrollView mà không bị sập?


351

Tôi đã tìm kiếm các giải pháp cho vấn đề này và câu trả lời duy nhất tôi có thể tìm thấy là " không đặt ListView vào ScrollView ". Tôi vẫn chưa thấy bất kỳ lời giải thích thực sự cho lý do tại sao mặc dù. Lý do duy nhất tôi dường như có thể tìm thấy là Google không nghĩ rằng bạn nên muốn làm điều đó. Vâng, tôi đã làm, vì vậy tôi đã làm.

Vì vậy, câu hỏi là, làm thế nào bạn có thể đặt ListView vào ScrollView mà không bị sập xuống chiều cao tối thiểu của nó?


11
Bởi vì khi một thiết bị sử dụng màn hình cảm ứng, không có cách nào tốt để phân biệt giữa hai thùng chứa có thể cuộn được lồng vào nhau. Nó hoạt động trên máy tính để bàn truyền thống nơi bạn sử dụng thanh cuộn để cuộn một container.
Romain Guy

57
Chắc chắn là có. Nếu chúng chạm vào bên trong cái bên trong, hãy cuộn nó. Nếu cái bên trong quá lớn, đó là thiết kế UI xấu. Điều đó không có nghĩa là nó là một ý tưởng tồi nói chung.
DougW

5
Chỉ cần vấp phải điều này cho ứng dụng Tablet của tôi. Mặc dù nó dường như không quá tệ trên điện thoại thông minh, nhưng điều này thật kinh khủng trên máy tính bảng nơi bạn có thể dễ dàng quyết định liệu người dùng muốn cuộn ScrollView bên ngoài hay bên trong. @DougW: Thiết kế khủng khiếp, tôi đồng ý.
sứa

4
@Romain - đây là một bài viết khá cũ, vì vậy tôi tự hỏi liệu những lo ngại ở đây đã được giải quyết chưa, hoặc vẫn không có cách nào tốt để sử dụng ListView bên trong ScrollView (hoặc một cách thay thế không liên quan đến mã hóa thủ công tất cả các tính năng của ListView vào linearLayout)?
Jim

3
@Jim: "hoặc một giải pháp thay thế không liên quan đến việc mã hóa thủ công tất cả các tính năng của ListView vào Tuyến tính" - đặt mọi thứ vào trong ListView. ListViewcó thể có nhiều kiểu hàng, cộng với các hàng không thể chọn.
CommonsWare

Câu trả lời:


196

Sử dụng một ListViewđể làm cho nó không cuộn là cực kỳ tốn kém và đi ngược lại toàn bộ mục đích ListView. Bạn KHÔNG nên làm điều này. Chỉ cần sử dụng một LinearLayoutthay thế.


69
Nguồn sẽ là tôi vì tôi đã phụ trách ListView trong 2 hoặc 3 năm qua :) ListView thực hiện rất nhiều công việc phụ để tối ưu hóa việc sử dụng bộ điều hợp. Cố gắng làm việc xung quanh nó vẫn sẽ khiến ListView thực hiện rất nhiều công việc mà linearLayout sẽ không phải làm. Tôi sẽ không đi vào chi tiết vì không có đủ chỗ ở đây để giải thích việc triển khai ListView. Điều này cũng sẽ không giải quyết được cách ListView hành xử liên quan đến các sự kiện. Cũng không có gì đảm bảo rằng nếu bản hack của bạn hoạt động ngày hôm nay thì nó vẫn hoạt động trong phiên bản tương lai. Sử dụng linearLayout sẽ không thực sự hiệu quả.
Romain Guy

7
Vâng đó là một phản ứng hợp lý. Nếu tôi có thể chia sẻ một số phản hồi, tôi có trải nghiệm iPhone quan trọng hơn nhiều và tôi có thể nói với bạn rằng tài liệu của Apple được viết tốt hơn nhiều về các đặc điểm hiệu suất và các trường hợp sử dụng (hoặc chống mẫu) như thế này. Nhìn chung, tài liệu Android được phân phối nhiều hơn và ít tập trung hơn. Tôi hiểu có một số lý do đằng sau đó, nhưng đó là một cuộc thảo luận dài, vì vậy nếu bạn cảm thấy bắt buộc phải trò chuyện về nó, hãy cho tôi biết.
DougW

6
Bạn có thể dễ dàng viết một phương thức tĩnh tạo ra linearLayout từ Adaptor.
Romain Guy

125
Đây KHÔNG phải là câu trả lời cho How can I put a ListView into a ScrollView without it collapsing?... Bạn có thể nói rằng bạn không nên làm điều đó, nhưng cũng đưa ra câu trả lời về cách thực hiện, đó là một câu trả lời tốt. Bạn biết rằng ListView có một số tính năng thú vị khác ngoài việc cuộn, các tính năng mà linearLayout không có.
Jorge Fuentes González

44
Điều gì xảy ra nếu bạn có một vùng chứa ListView + Chế độ xem khác và bạn muốn mọi thứ cuộn thành một? Đó có vẻ như là một trường hợp sử dụng hợp lý để đặt ListView trong ScrollView. Thay thế ListView bằng linearLayout không phải là một giải pháp vì sau đó bạn không thể sử dụng Bộ điều hợp.
Barry Fruitman

262

Đây là giải pháp của tôi. Tôi khá mới đối với nền tảng Android và tôi chắc chắn rằng điều này hơi rắc rối, đặc biệt là trong phần gọi trực tiếp .measure và đặt thuộc LayoutParams.heighttính trực tiếp, nhưng nó hoạt động.

Tất cả bạn phải làm là gọi Utility.setListViewHeightBasedOnChildren(yourListView)và nó sẽ được thay đổi kích thước để phù hợp với chiều cao của vật phẩm.

public class Utility {
    public static void setListViewHeightBasedOnChildren(ListView listView) {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) {
            // pre-condition
            return;
        }

        int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();

        for (int i = 0; i < listAdapter.getCount(); i++) {
            View listItem = listAdapter.getView(i, null, listView);
            if (listItem instanceof ViewGroup) {
                listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
             }

             listItem.measure(0, 0);
             totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
    }
}

66
Bạn vừa tạo lại một tuyến tính rất đắt tiền :)
Romain Guy

82
Ngoại trừ một cái LinearLayoutkhông có tất cả các đặc tính vốn có của một ListView- nó không có bộ chia, chế độ xem tiêu đề / chân trang, bộ chọn danh sách phù hợp với màu chủ đề UI của điện thoại, v.v ... Thành thật mà nói, không có lý do gì bạn không thể cuộn thùng chứa bên trong cho đến khi nó kết thúc và sau đó nó dừng chặn các sự kiện chạm để thùng chứa bên ngoài cuộn lại. Mỗi trình duyệt máy tính để bàn thực hiện việc này khi bạn đang sử dụng bánh xe cuộn của mình. Bản thân Android có thể xử lý cuộn lồng nhau nếu bạn điều hướng qua bi xoay, vậy tại sao không qua cảm ứng?
Neil Traft

29
listItem.measure(0,0)sẽ ném NPE nếu listItem là một phiên bản Viewgroup. Tôi đã thêm vào như sau listItem.measure:if (listItem instanceof ViewGroup) listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
Siklab.ph

4
Sửa lỗi cho ListViews với phần đệm khác 0:int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
Paul

8
Thật không may phương pháp này là một chút thiếu sót khi ListViewcó thể chứa các mặt hàng có chiều cao thay đổi. Cuộc gọi getMeasuredHeight()chỉ trả về chiều cao tối thiểu được nhắm mục tiêu trái ngược với chiều cao thực tế. Tôi đã thử sử dụng getHeight()thay thế, nhưng điều này trả về 0. Có ai có cái nhìn sâu sắc về cách tiếp cận này không?
Matt Huggins

89

Điều này chắc chắn sẽ hoạt động ............
Bạn phải thay thế <ScrollView ></ScrollView>tệp XML trong bố cục của mình bằng tệp này Custom ScrollViewnhư thế này<com.tmd.utils.VerticalScrollview > </com.tmd.utils.VerticalScrollview >

package com.tmd.utils;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ScrollView;

public class VerticalScrollview extends ScrollView{

    public VerticalScrollview(Context context) {
        super(context);
    }

     public VerticalScrollview(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public VerticalScrollview(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                    Log.i("VerticalScrollview", "onInterceptTouchEvent: DOWN super false" );
                    super.onTouchEvent(ev);
                    break;

            case MotionEvent.ACTION_MOVE:
                    return false; // redirect MotionEvents to ourself

            case MotionEvent.ACTION_CANCEL:
                    Log.i("VerticalScrollview", "onInterceptTouchEvent: CANCEL super false" );
                    super.onTouchEvent(ev);
                    break;

            case MotionEvent.ACTION_UP:
                    Log.i("VerticalScrollview", "onInterceptTouchEvent: UP super false" );
                    return false;

            default: Log.i("VerticalScrollview", "onInterceptTouchEvent: " + action ); break;
        }

        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        super.onTouchEvent(ev);
        Log.i("VerticalScrollview", "onTouchEvent. action: " + ev.getAction() );
         return true;
    }
}

4
Điều này là rất tốt. Nó cũng cho phép đặt một mảnh Google Maps bên trong ScrollView và nó vẫn hoạt động phóng to / thu nhỏ. Cảm ơn.
Magnus Johansson

Có ai nhận thấy bất kỳ khuyết điểm với phương pháp này? Thật đơn giản, tôi sợ: o
Marija Milosevic

1
Giải pháp hay, nên là câu trả lời được chấp nhận +1! Tôi đã trộn nó với câu trả lời dưới đây (từ djunod), và điều đó thật tuyệt!
tanou

1
Đây là cách khắc phục duy nhất giúp tôi cuộn và NHẤP VÀO trẻ em làm việc cùng lúc với tôi. Cảm ơn rất nhiều
pellyadolfo

25

Ngay khi đặt ListViewbên trong a ScrollView, chúng ta có thể sử dụng ListViewnhư một ScrollView. Những thứ phải có trong ListViewcó thể được đặt bên trong ListView. Các bố cục khác ở trên cùng và dưới cùng ListViewcó thể được đặt bằng cách thêm bố cục vào tiêu đề và chân trang của ListView. Vì vậy, toàn bộ ListViewsẽ cung cấp cho bạn một kinh nghiệm di chuyển.


3
Đây là câu trả lời imho thanh lịch và thích hợp nhất. Để biết thêm chi tiết về phương pháp này, hãy xem câu trả lời này: stackoverflow.com/a/20475821/1221537
adamdport 10/03/2015

21

rất nhiều tình huống có ý nghĩa khi có ListView trong ScrollView.

Đây là mã dựa trên đề xuất của DougW ... hoạt động trong một đoạn, chiếm ít bộ nhớ hơn.

public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null) {
        return;
    }
    int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
    int totalHeight = 0;
    View view = null;
    for (int i = 0; i < listAdapter.getCount(); i++) {
        view = listAdapter.getView(i, view, listView);
        if (i == 0) {
            view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, LayoutParams.WRAP_CONTENT));
        }
        view.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
        totalHeight += view.getMeasuredHeight();
    }
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}

gọi setListViewHeightBasingOnChildren (listview) trên mỗi listview được nhúng.


Nó sẽ không hoạt động khi các mục danh sách có kích thước thay đổi, tức là một số textview mở rộng trên nhiều dòng. Bất kì giải pháp nào?
Khobaib

Kiểm tra nhận xét của tôi ở đây - nó đưa ra giải pháp hoàn hảo - stackoverflow.com/a/23315696/1433187
Khobaib

đã làm việc với tôi với API 19, nhưng không phải với API 23: Ở đây, nó thêm nhiều khoảng trắng bên dưới listView.
Lucker10

17

ListView thực sự đã có khả năng tự đo đủ cao để hiển thị tất cả các mục, nhưng nó không làm điều này khi bạn chỉ định chỉ định quấn_content (ĐoSpec.UNSPECifyED). Nó sẽ làm điều này khi được đưa ra một chiều cao với Số đoSpec.AT_MOST. Với kiến ​​thức này, bạn có thể tạo một lớp con rất đơn giản để giải quyết vấn đề này hoạt động tốt hơn nhiều so với bất kỳ giải pháp nào được đăng ở trên. Bạn vẫn nên sử dụng quấn_content với lớp con này.

public class ListViewForEmbeddingInScrollView extends ListView {
    public ListViewForEmbeddingInScrollView(Context context) {
        super(context);
    }

    public ListViewForEmbeddingInScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ListViewForEmbeddingInScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 4, MeasureSpec.AT_MOST));
    }
}

Thao tác với heightMeasureSpec là AT_MOST với kích thước rất lớn (Integer.MAX_VALUE >> 4) khiến ListView đo được tất cả các con của nó theo chiều cao (rất lớn) nhất định và đặt chiều cao phù hợp.

Điều này hoạt động tốt hơn so với các giải pháp khác vì một vài lý do:

  1. Nó đo lường mọi thứ một cách chính xác (đệm, chia)
  2. Nó đo ListView trong suốt quá trình đo
  3. Do số 2, nó xử lý các thay đổi về chiều rộng hoặc số lượng vật phẩm một cách chính xác mà không cần bất kỳ mã bổ sung nào

Mặt khác, bạn có thể lập luận rằng việc làm này dựa vào hành vi không có giấy tờ trong SDK có thể thay đổi. Mặt khác, bạn có thể lập luận rằng đây là cách quấn_content thực sự hoạt động với ListView và hành vi quấn_content hiện tại bị phá vỡ.

Nếu bạn lo lắng rằng hành vi có thể thay đổi trong tương lai, bạn chỉ cần sao chép chức năng onMeasure và các chức năng liên quan ra khỏi ListView.java và vào lớp con của riêng bạn, sau đó tạo đường dẫn AT_MOST thông qua onMeasure để chạy UNSPECifyED.

Nhân tiện, tôi tin rằng đây là một cách tiếp cận hoàn toàn hợp lệ khi bạn đang làm việc với một số lượng nhỏ các mục danh sách. Nó có thể không hiệu quả khi so sánh với linearLayout, nhưng khi số lượng vật phẩm ít, sử dụng linearLayout là tối ưu hóa không cần thiết và do đó độ phức tạp không cần thiết.


Cũng hoạt động với các mục chiều cao thay đổi trong listview!
Denny

@Jason Y điều này làm gì, Integer.MAX_VALUE >> 4 nghĩa là gì ?? Có 4 cái gì vậy ??
KJEjava48

@Jason Y đối với tôi nó chỉ hoạt động sau khi tôi thay đổi Integer.MAX_VALUE >> 2 thành Integer.MAX_VALUE >> 21. Tại sao nó như vậy? Ngoài ra, nó hoạt động với ScrollView chứ không phải với NestedScrollView.
KJEjava48

13

Có một thiết lập tích hợp cho nó. Trên ScrollView:

android:fillViewport="true"

Trong Java,

mScrollView.setFillViewport(true);

Romain Guy giải thích sâu về vấn đề này tại đây: http://www.cquil-creature.org/2010/08/15/scrollviews-handy-trick/


2
Điều đó làm việc cho một linearLayout như anh ta chứng minh. Nó không hoạt động cho ListView. Dựa vào ngày của bài đăng đó, tôi tưởng tượng anh ấy đã viết nó để cung cấp một ví dụ về sự thay thế của anh ấy, nhưng điều này không trả lời câu hỏi.
DougW

đây là giải pháp nhanh

10

Bạn tạo ListView tùy chỉnh không thể cuộn

public class NonScrollListView extends ListView {

    public NonScrollListView(Context context) {
        super(context);
    }
    public NonScrollListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public NonScrollListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
                    Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
            ViewGroup.LayoutParams params = getLayoutParams();
            params.height = getMeasuredHeight();    
    }
}

Trong tệp tài nguyên bố cục của bạn

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <!-- com.Example Changed with your Package name -->

    <com.Example.NonScrollListView
        android:id="@+id/lv_nonscroll_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
    </com.Example.NonScrollListView>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/lv_nonscroll_list" >

        <!-- Your another layout in scroll view -->

    </RelativeLayout>
</RelativeLayout>

Trong tệp Java

Tạo một đối tượng của customListview của bạn thay vì ListView như: NonScrollListView non_scroll_list = (NonScrollListView) findViewById (R.id.lv_nonscroll_list);


8

Chúng tôi không thể sử dụng hai simulteniuosly cuộn. Chúng tôi sẽ có tổng chiều dài ListView và mở rộng listview với tổng chiều cao. Sau đó, chúng tôi có thể thêm ListView trong ScrollView trực tiếp hoặc sử dụng linearLayout vì ScrollView có trực tiếp một con. sao chép phương thức setListViewHeightBasingOnChildren (lv) trong mã của bạn và mở rộng listview sau đó bạn có thể sử dụng listview bên trong scrollview. \ bố trí tệp xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
 <ScrollView

        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
         android:background="#1D1D1D"
        android:orientation="vertical"
        android:scrollbars="none" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="#1D1D1D"
            android:orientation="vertical" >

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="40dip"
                android:background="#333"
                android:gravity="center_vertical"
                android:paddingLeft="8dip"
                android:text="First ListView"
                android:textColor="#C7C7C7"
                android:textSize="20sp" />

            <ListView
                android:id="@+id/first_listview"
                android:layout_width="260dp"
                android:layout_height="wrap_content"
                android:divider="#00000000"
               android:listSelector="#ff0000"
                android:scrollbars="none" />

               <TextView
                android:layout_width="fill_parent"
                android:layout_height="40dip"
                android:background="#333"
                android:gravity="center_vertical"
                android:paddingLeft="8dip"
                android:text="Second ListView"
                android:textColor="#C7C7C7"
                android:textSize="20sp" />

            <ListView
                android:id="@+id/secondList"
                android:layout_width="260dp"
                android:layout_height="wrap_content"
                android:divider="#00000000"
                android:listSelector="#ffcc00"
                android:scrollbars="none" />
  </LinearLayout>
  </ScrollView>

   </LinearLayout>

Phương thức onCreate trong lớp Activity:

 import java.util.ArrayList;
  import android.app.Activity;
 import android.os.Bundle;
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
 import android.widget.ListAdapter;
  import android.widget.ListView;

   public class MainActivity extends Activity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.listview_inside_scrollview);
    ListView list_first=(ListView) findViewById(R.id.first_listview);
    ListView list_second=(ListView) findViewById(R.id.secondList);
    ArrayList<String> list=new ArrayList<String>();
    for(int x=0;x<30;x++)
    {
        list.add("Item "+x);
    }

       ArrayAdapter<String> adapter=new ArrayAdapter<String>(getApplicationContext(), 
          android.R.layout.simple_list_item_1,list);               
      list_first.setAdapter(adapter);

     setListViewHeightBasedOnChildren(list_first);

      list_second.setAdapter(adapter);

    setListViewHeightBasedOnChildren(list_second);
   }



   public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null) {
        // pre-condition
        return;
    }

    int totalHeight = 0;
    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);
        listItem.measure(0, 0);
        totalHeight += listItem.getMeasuredHeight();
    }

    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight
            + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
      }

Sửa chữa tốt! Nó hoạt động khi cần thiết. Cảm ơn. Trong trường hợp của tôi, tôi có cả đống khung nhìn trước danh sách và giải pháp hoàn hảo là chỉ có một cuộn dọc trên khung nhìn.
Tục ngữ

4

Đây là sự kết hợp các câu trả lời của DougW, Good Guy Greg và Paul. Tôi thấy tất cả đều cần thiết khi cố gắng sử dụng điều này với bộ điều hợp listview tùy chỉnh và các mục danh sách không chuẩn nếu không listview đã làm hỏng ứng dụng (cũng bị lỗi với câu trả lời của Nex):

public void setListViewHeightBasedOnChildren(ListView listView) {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) {
            return;
        }

        int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
        for (int i = 0; i < listAdapter.getCount(); i++) {
            View listItem = listAdapter.getView(i, null, listView);
            if (listItem instanceof ViewGroup)
                listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
            listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
    }

Gọi phương pháp này ở đâu?
Dhrupal

Sau khi tạo listview và thiết lập bộ chuyển đổi.
Giỏ hàng bị bỏ rơi

Điều này trở nên quá chậm trên một danh sách dài.
cakan

@cakan Nó cũng được viết trước đó 2 năm như một cách giải quyết để cho phép sử dụng một số thành phần UI nhất định mà không thực sự có nghĩa là được kết hợp. Bạn đang thêm một lượng chi phí không cần thiết trong Android hiện đại, do đó, dự kiến ​​danh sách sẽ chậm lại khi nó lớn.
Giỏ hàng bị bỏ rơi

3

Bạn không nên đặt ListView trong ScrollView vì ListView đã ScrollView. Vì vậy, nó sẽ giống như đặt ScrollView trong ScrollView.

Bạn đang cố gắng để thực hiện?


4
Tôi đang cố gắng hoàn thành việc có ListView bên trong ScrollView. Tôi không muốn ListView của mình có thể cuộn được, nhưng không có thuộc tính đơn giản nào để đặt myListView.scrollEnabled = false;
DougW

Như Romain Guy đã nói, đây không phải là mục đích của ListView. ListView là một chế độ xem được tối ưu hóa để hiển thị một danh sách dài các mục, với rất nhiều logic phức tạp để lười tải các chế độ xem, sử dụng lại các chế độ xem, v.v. Nếu bạn chỉ muốn một loạt các mục trong một cột dọc, hãy sử dụng Tuyến tính.
Cheryl Simon

9
Vấn đề là có một số giải pháp mà ListView cung cấp, bao gồm cả giải pháp bạn đề cập. Phần lớn các chức năng mà Bộ điều hợp đơn giản hóa là về quản lý và kiểm soát dữ liệu, không phải tối ưu hóa giao diện người dùng. IMO nên có một ListView đơn giản và một ListView phức tạp hơn thực hiện tất cả các mục và công cụ tái sử dụng mục danh sách.
DougW

2
Hoàn toàn đồng ý. Lưu ý rằng thật dễ dàng để sử dụng Bộ điều hợp hiện có với linearLayout, nhưng tôi muốn tất cả những điều hay mà ListView cung cấp, như bộ chia và tôi không cảm thấy muốn triển khai thủ công.
Artem Russakovskii

1
@ArtemRussakovskii Bạn có thể giải thích cách dễ dàng của mình để thay thế ListView thành linearLayout bằng một Adaptor hiện có không?
happyhardik

3

Tôi đã chuyển đổi @ DougW's Utilitythành C # (được sử dụng trong Xamarin). Các mục sau hoạt động tốt đối với các mục có chiều cao cố định trong danh sách và sẽ hầu như ổn, hoặc ít nhất là khởi đầu tốt, nếu chỉ một số mục lớn hơn một chút so với mục tiêu chuẩn.

// You will need to put this Utility class into a code file including various
// libraries, I found that I needed at least System, Linq, Android.Views and 
// Android.Widget.
using System;
using System.Linq;
using Android.Views;
using Android.Widget;

namespace UtilityNamespace  // whatever you like, obviously!
{
    public class Utility
    {
        public static void setListViewHeightBasedOnChildren (ListView listView)
        {
            if (listView.Adapter == null) {
                // pre-condition
                return;
            }

            int totalHeight = listView.PaddingTop + listView.PaddingBottom;
            for (int i = 0; i < listView.Count; i++) {
                View listItem = listView.Adapter.GetView (i, null, listView);
                if (listItem.GetType () == typeof(ViewGroup)) {
                    listItem.LayoutParameters = new LinearLayout.LayoutParams (ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
                }
                listItem.Measure (0, 0);
                totalHeight += listItem.MeasuredHeight;
            }

            listView.LayoutParameters.Height = totalHeight + (listView.DividerHeight * (listView.Count - 1));
        }
    }
}

Cảm ơn @DougW, điều này đã giúp tôi thoát khỏi tình trạng khó khăn khi tôi phải làm việc với OtherP People'sCode. :-)


Một câu hỏi: Khi nào bạn gọi setListViewHeightBasedOnChildren()phương thức? Bởi vì nó không hoạt động tất cả thời gian cho tôi.
Alexandre D.

@AlexandreD bạn gọi Utility.setListViewHeightBasingOnChildren (yourListView) khi bạn đã tạo danh sách các mục. tức là đảm bảo rằng bạn đã tạo danh sách của bạn đầu tiên! Tôi đã thấy rằng tôi đang lập một danh sách và thậm chí tôi còn hiển thị các mục của mình trong Console.WriteLine ... nhưng bằng cách nào đó, các mục đó nằm trong phạm vi sai. Khi tôi đã tìm ra điều đó, và đảm bảo rằng tôi có phạm vi phù hợp cho danh sách của mình, điều này hoạt động như một cơ duyên.
Phil Ryan

Thực tế là danh sách của tôi được tạo trong ViewModel của tôi. Làm cách nào tôi có thể đợi danh sách của mình được tạo trong chế độ xem trước khi có bất kỳ lệnh gọi nào đến Utility.setListViewHeightBasingOnChildren (yourListView)?
Alexandre D.

3

Đây là điều duy nhất làm việc cho tôi:

trên Lollipop trở đi bạn có thể sử dụng

yourtListView.setNestedScrollingEnabled(true);

Điều này cho phép hoặc vô hiệu hóa cuộn lồng nhau cho chế độ xem này nếu bạn cần khả năng tương thích ngược với phiên bản cũ hơn của HĐH, bạn sẽ phải sử dụng RecyclerView.


Điều này không có bất kỳ ảnh hưởng nào đến lượt xem danh sách của tôi về API 22 trong DialogFragment trong hoạt động AppCompat. Họ vẫn sụp đổ. Câu trả lời của @Phil Ryan đã làm việc với một chút sửa đổi.
Hakan elik MK1

3

Trước khi nó không thể. Nhưng với việc phát hành thư viện Appcompat mới và thư viện Design, điều này có thể đạt được.

Bạn chỉ cần sử dụng NestedScrollView https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html

Tôi không biết nó có hoạt động với Listview hay không nhưng hoạt động với RecyclerView.

Đoạn mã:

<android.support.v4.widget.NestedScrollView 
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</android.support.v4.widget.NestedScrollView>

Thật tuyệt, tôi đã không thử nó vì vậy tôi không thể xác nhận rằng ListView nội bộ không bị sập, nhưng có vẻ như đó có thể là ý định. Thật buồn khi không thấy Romain Guy trong nhật ký đổ lỗi;)
DougW

1
Điều này hoạt động với recyclerView nhưng không listView. Thanx
IgniteCoders

2

này, tôi đã có một vấn đề tương tự Tôi muốn hiển thị chế độ xem danh sách không cuộn và tôi thấy rằng việc thao tác các tham số hoạt động nhưng không hiệu quả và sẽ hoạt động khác nhau trên các thiết bị khác nhau .. kết quả là đây là một đoạn mã lịch trình của tôi thực sự làm việc này rất hiệu quả .

db = new dbhelper(this);

 cursor = db.dbCursor();
int count = cursor.getCount();
if (count > 0)
{    
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.layoutId);
startManagingCursor(YOUR_CURSOR);

YOUR_ADAPTER(**or SimpleCursorAdapter **) adapter = new YOUR_ADAPTER(this,
    R.layout.itemLayout, cursor, arrayOrWhatever, R.id.textViewId,
    this.getApplication());

int i;
for (i = 0; i < count; i++){
  View listItem = adapter.getView(i,null,null);
  linearLayout.addView(listItem);
   }
}

Lưu ý: nếu bạn sử dụng điều này, notifyDataSetChanged();sẽ không hoạt động như dự định vì các chế độ xem sẽ không được vẽ lại. Làm điều này nếu bạn cần một công việc xung quanh

adapter.registerDataSetObserver(new DataSetObserver() {

            @Override
            public void onChanged() {
                super.onChanged();
                removeAndRedrawViews();

            }

        });

2

Có hai vấn đề khi sử dụng ListView bên trong ScrollView.

1- ListView phải mở rộng hoàn toàn đến chiều cao con của nó. ListView này giải quyết điều này:

public class ListViewExpanded extends ListView
{
    public ListViewExpanded(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        setDividerHeight(0);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST));
    }
}

Chiều cao chia phải là 0, thay vào đó hãy sử dụng phần đệm trong các hàng.

2- ListView tiêu thụ các sự kiện chạm để ScrollView không thể cuộn như bình thường. ScrollView này giải quyết vấn đề này:

public class ScrollViewInterceptor extends ScrollView
{
    float startY;

    public ScrollViewInterceptor(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e)
    {
        onTouchEvent(e);
        if (e.getAction() == MotionEvent.ACTION_DOWN) startY = e.getY();
        return (e.getAction() == MotionEvent.ACTION_MOVE) && (Math.abs(startY - e.getY()) > 50);
    }
}

Đây là cách tốt nhất tôi tìm thấy để thực hiện các mẹo!


2

Một giải pháp tôi sử dụng là, để thêm tất cả Nội dung của ScrollView (những gì nên ở trên và dưới listView) dưới dạng headerView và footerView trong ListView.

Vì vậy, nó hoạt động như thế nào, cũng là convertview được nối lại như thế nào.


1

cảm ơn mã của Vinay đây là mã của tôi khi bạn không thể có một lượt xem trong một lượt xem nhưng bạn cần một cái gì đó như thế

LayoutInflater li = LayoutInflater.from(this);

                RelativeLayout parent = (RelativeLayout) this.findViewById(R.id.relativeLayoutCliente);

                int recent = 0;

                for(Contatto contatto : contatti)
                {
                    View inflated_layout = li.inflate(R.layout.header_listview_contatti, layout, false);


                    inflated_layout.setId(contatto.getId());
                    ((TextView)inflated_layout.findViewById(R.id.textViewDescrizione)).setText(contatto.getDescrizione());
                    ((TextView)inflated_layout.findViewById(R.id.textViewIndirizzo)).setText(contatto.getIndirizzo());
                    ((TextView)inflated_layout.findViewById(R.id.textViewTelefono)).setText(contatto.getTelefono());
                    ((TextView)inflated_layout.findViewById(R.id.textViewMobile)).setText(contatto.getMobile());
                    ((TextView)inflated_layout.findViewById(R.id.textViewFax)).setText(contatto.getFax());
                    ((TextView)inflated_layout.findViewById(R.id.textViewEmail)).setText(contatto.getEmail());



                    RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);

                    if (recent == 0)
                    {
                        relativeParams.addRule(RelativeLayout.BELOW, R.id.headerListViewContatti);
                    }
                    else
                    {
                        relativeParams.addRule(RelativeLayout.BELOW, recent);
                    }
                    recent = inflated_layout.getId();

                    inflated_layout.setLayoutParams(relativeParams);
                    //inflated_layout.setLayoutParams( new RelativeLayout.LayoutParams(source));

                    parent.addView(inflated_layout);
                }

RelLayout vẫn ở trong ScrollView để tất cả có thể cuộn được :)


1

Đây là một sửa đổi nhỏ trên câu trả lời của @djunod mà tôi cần để làm cho nó hoạt động hoàn hảo:

public static void setListViewHeightBasedOnChildren(ListView listView)
{
    ListAdapter listAdapter = listView.getAdapter();
    if(listAdapter == null) return;
    if(listAdapter.getCount() <= 1) return;

    int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
    int totalHeight = 0;
    View view = null;
    for(int i = 0; i < listAdapter.getCount(); i++)
    {
        view = listAdapter.getView(i, view, listView);
        view.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
        totalHeight += view.getMeasuredHeight();
    }
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}

: Xin chào, câu trả lời của bạn hoạt động hầu hết thời gian, nhưng nó không hoạt động nếu listview có chế độ xem tiêu đề và chế độ xem chân. Bạn có thể kiểm tra nó không?
hguser

Tôi cần một giải pháp khi các mục danh sách có kích thước thay đổi, tức là một số textview mở rộng trên nhiều dòng. Bất cứ đề nghị nào?
Khobaib

1

Hãy thử cái này, cái này hiệu quả với tôi, tôi quên mất tôi đã tìm thấy nó ở đâu, ở đâu đó trong stack stack, tôi không ở đây để giải thích tại sao nó không hoạt động, nhưng đây là câu trả lời :).

    final ListView AturIsiPulsaDataIsiPulsa = (ListView) findViewById(R.id.listAturIsiPulsaDataIsiPulsa);
    AturIsiPulsaDataIsiPulsa.setOnTouchListener(new ListView.OnTouchListener() 
    {
        @Override
        public boolean onTouch(View v, MotionEvent event) 
        {
            int action = event.getAction();
            switch (action) 
            {
                case MotionEvent.ACTION_DOWN:
                // Disallow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(true);
                break;

                case MotionEvent.ACTION_UP:
                // Allow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(false);
                break;
            }

            // Handle ListView touch events.
            v.onTouchEvent(event);
            return true;
        }
    });
    AturIsiPulsaDataIsiPulsa.setClickable(true);
    AturIsiPulsaDataIsiPulsa.setAdapter(AturIsiPulsaDataIsiPulsaAdapter);

EDIT!, Cuối cùng tôi đã tìm ra nơi tôi nhận được mã. đây ! : ListView bên trong ScrollView không cuộn trên Android


Điều này bao gồm cách ngăn chặn mất tương tác cuộn, nhưng câu hỏi là về việc duy trì chiều cao của lượt xem.
Giỏ hàng bị bỏ rơi

1

Mặc dù các phương thức setListViewHeightBasingOnChildren () được đề xuất hoạt động trong hầu hết các trường hợp, trong một số trường hợp, đặc biệt với rất nhiều mục, tôi nhận thấy rằng các phần tử cuối cùng không được hiển thị. Vì vậy, tôi đã quyết định bắt chước một phiên bản đơn giản của hành vi ListView để sử dụng lại bất kỳ mã Adaptor nào, đây là giải pháp thay thế ListView:

import android.content.Context;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ListAdapter;

public class StretchedListView extends LinearLayout {

private final DataSetObserver dataSetObserver;
private ListAdapter adapter;
private OnItemClickListener onItemClickListener;

public StretchedListView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setOrientation(LinearLayout.VERTICAL);
    this.dataSetObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            syncDataFromAdapter();
            super.onChanged();
        }

        @Override
        public void onInvalidated() {
            syncDataFromAdapter();
            super.onInvalidated();
        }
    };
}

public void setAdapter(ListAdapter adapter) {
    ensureDataSetObserverIsUnregistered();

    this.adapter = adapter;
    if (this.adapter != null) {
        this.adapter.registerDataSetObserver(dataSetObserver);
    }
    syncDataFromAdapter();
}

protected void ensureDataSetObserverIsUnregistered() {
    if (this.adapter != null) {
        this.adapter.unregisterDataSetObserver(dataSetObserver);
    }
}

public Object getItemAtPosition(int position) {
    return adapter != null ? adapter.getItem(position) : null;
}

public void setSelection(int i) {
    getChildAt(i).setSelected(true);
}

public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
    this.onItemClickListener = onItemClickListener;
}

public ListAdapter getAdapter() {
    return adapter;
}

public int getCount() {
    return adapter != null ? adapter.getCount() : 0;
}

private void syncDataFromAdapter() {
    removeAllViews();
    if (adapter != null) {
        int count = adapter.getCount();
        for (int i = 0; i < count; i++) {
            View view = adapter.getView(i, null, this);
            boolean enabled = adapter.isEnabled(i);
            if (enabled) {
                final int position = i;
                final long id = adapter.getItemId(position);
                view.setOnClickListener(new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        if (onItemClickListener != null) {
                            onItemClickListener.onItemClick(null, v, position, id);
                        }
                    }
                });
            }
            addView(view);

        }
    }
}
}

1

Tất cả những câu trả lời này đều sai !!! Nếu bạn đang cố gắng đưa listview vào chế độ xem cuộn, bạn nên nghĩ lại thiết kế của mình. Bạn đang cố gắng đặt ScrollView trong ScrollView. Can thiệp vào danh sách sẽ làm tổn thương hiệu suất danh sách. Nó được thiết kế giống như thế này bởi Android.

Nếu bạn thực sự muốn danh sách nằm trong cùng một cuộn với các yếu tố khác, tất cả những gì bạn phải làm là thêm các mục khác vào đầu danh sách bằng cách sử dụng câu lệnh chuyển đổi đơn giản trong bộ điều hợp của bạn:

class MyAdapter extends ArrayAdapter{

    public MyAdapter(Context context, int resource, List objects) {
        super(context, resource, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
         ViewItem viewType = getItem(position);

        switch(viewType.type){
            case TEXTVIEW:
                convertView = layouteInflater.inflate(R.layout.textView1, parent, false);

                break;
            case LISTITEM:
                convertView = layouteInflater.inflate(R.layout.listItem, parent, false);

                break;            }


        return convertView;
    }


}

Bộ điều hợp danh sách có thể xử lý tất cả mọi thứ vì nó chỉ hiển thị những gì có thể nhìn thấy.


0

Toàn bộ vấn đề này sẽ biến mất nếu linearLayout có phương thức setAd CHƯƠNG, bởi vì sau đó khi bạn bảo ai đó sử dụng nó thay vào đó thì việc thay thế sẽ không đáng kể.

Nếu bạn thực sự muốn có một ListView cuộn bên trong một chế độ xem cuộn khác thì điều này sẽ không có ích, nhưng nếu không thì điều này ít nhất sẽ cho bạn một ý tưởng.

Bạn cần tạo một bộ điều hợp tùy chỉnh để kết hợp tất cả nội dung bạn muốn cuộn qua và đặt bộ điều hợp của ListView thành đó.

Tôi không có mã mẫu tiện dụng, nhưng nếu bạn muốn một cái gì đó như thế.

<ListView/>

(other content)

<ListView/>

Sau đó, bạn cần tạo một bộ chuyển đổi đại diện cho tất cả nội dung đó. ListView / Adapters cũng đủ thông minh để xử lý các loại khác nhau, nhưng bạn cần phải tự viết bộ điều hợp.

API giao diện người dùng Android không hoàn thiện như hầu hết mọi thứ khác, vì vậy nó không có cùng tính năng như các nền tảng khác. Ngoài ra, khi làm một cái gì đó trên Android, bạn cần phải có một tư duy Android (unix), nơi bạn mong muốn làm bất cứ điều gì có lẽ bạn sẽ phải lắp ráp chức năng của các phần nhỏ hơn và viết một loạt mã của riêng bạn để có được nó công việc.


0

Khi chúng ta đặt ListViewbên trong ScrollViewhai vấn đề phát sinh. Một là ScrollViewđo các con của nó ở chế độ KHÔNG KIẾM, do đó, ListViewđặt chiều cao của riêng nó để chỉ chứa một mục (tôi không biết tại sao), một mục khác là ScrollViewchặn sự kiện chạmListView không cuộn.

Nhưng chúng ta có thể đặt ListViewbên trong ScrollViewvới một số cách giải quyết. Bài đăng này , bởi tôi, giải thích cách giải quyết. Bằng cách giải quyết này, chúng tôi cũng có thể giữ lại ListViewtính năng tái chế.


0

Thay vì đặt listview bên trong Scrollview, hãy đặt phần còn lại của nội dung giữa listview và mở Scrollview thành một chế độ xem riêng biệt và đặt chế độ xem đó làm tiêu đề của listview. Vì vậy, cuối cùng bạn sẽ chỉ xem danh sách phụ trách Scroll.



-1

Đây là phiên bản mã của tôi tính tổng chiều cao của chế độ xem danh sách. Cái này hoạt động với tôi:

   public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null || listAdapter.getCount() < 2) {
        // pre-condition
        return;
    }

    int totalHeight = 0;
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(BCTDApp.getDisplaySize().width, View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);
        if (listItem instanceof ViewGroup) listItem.setLayoutParams(lp);
        listItem.measure(widthMeasureSpec, heightMeasureSpec);
        totalHeight += listItem.getMeasuredHeight();
    }

    totalHeight += listView.getPaddingTop() + listView.getPaddingBottom();
    totalHeight += (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight;
    listView.setLayoutParams(params);
    listView.requestLayout();
}
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.