Jak mogę umieścić ListView w ScrollView bez jego zwijania?

351

Szukałem rozwiązań tego problemu, a jedyną odpowiedzią, jaką mogę znaleźć, wydaje się być „ nie umieszczaj ListView w ScrollView ”. Mam jeszcze zobaczyć żadnego rzeczywistego wyjaśnienia , dlaczego mimo. Wydaje mi się, że jedynym powodem jest to, że Google uważa, że ​​nie powinieneś tego robić. Cóż, tak zrobiłem.

Pytanie brzmi: w jaki sposób można umieścić ListView w ScrollView bez zwinięcia do jego minimalnej wysokości?

DougW
źródło
11
Ponieważ gdy urządzenie korzysta z ekranu dotykowego, nie ma dobrego sposobu na rozróżnienie dwóch zagnieżdżonych pojemników przewijalnych. Działa na tradycyjnych komputerach stacjonarnych, w których używa się paska przewijania do przewijania kontenera.
Romain Guy
57
Pewnie że jest. Jeśli dotkną wewnątrz, przewiń to. Jeśli wewnętrzny jest zbyt duży, to zły projekt interfejsu użytkownika. To nie znaczy, że ogólnie jest to zły pomysł.
DougW
5
Właśnie natknąłem się na to w mojej aplikacji na tablet. Chociaż nie wydaje się być gorzej na smartfonie, jest to okropne na tablecie, w którym łatwo można zdecydować, czy użytkownik chce przewinąć zewnętrzny lub wewnętrzny ScrollView. @DougW: Okropny projekt, zgadzam się.
meduza
4
@Romain - jest to dość stary post, więc zastanawiam się, czy wątpliwości tutaj zostały już rozwiązane, czy też nie ma dobrego sposobu na użycie ListView wewnątrz ScrollView (lub alternatywy, która nie wymaga ręcznego kodowania wszystkie zalety ListView do LinearLayout)?
Jim
3
@Jim: „lub alternatywa, która nie wymaga ręcznego kodowania wszystkich zalet ListView w LinearLayout” - włóż wszystko do ListView. ListViewmoże mieć wiele stylów wierszy oraz wiersze, których nie można wybrać.
CommonsWare

Odpowiedzi:

196

Używanie, ListViewaby nie przewijało się, jest niezwykle drogie i jest sprzeczne z całym celem ListView. NIE powinieneś tego robić. Po prostu użyj LinearLayoutzamiast tego.

Romain Guy
źródło
69
Źródłem byłoby ja, odkąd kieruję ListView przez ostatnie 2 lub 3 lata :) ListView wykonuje wiele dodatkowej pracy, aby zoptymalizować użycie adapterów. Próba obejścia tego nadal spowoduje, że ListView wykona wiele pracy, której LinearLayout nie musiałby wykonywać. Nie będę wchodził w szczegóły, ponieważ nie ma tu wystarczająco dużo miejsca, aby wyjaśnić implementację ListView. To również nie rozwiąże sposobu, w jaki zachowuje się ListView w odniesieniu do zdarzeń dotykowych. Nie ma również gwarancji, że jeśli Twój hack działa dzisiaj, nadal będzie działał w przyszłej wersji. Korzystanie z LinearLayout nie byłoby tak naprawdę pracą.
Romain Guy
7
To rozsądna odpowiedź. Gdybym mógł podzielić się opiniami, mam znacznie większe doświadczenie w korzystaniu z iPhone'a i mogę powiedzieć, że dokumentacja Apple'a jest znacznie lepsza w odniesieniu do cech wydajności i przypadków użycia (lub anty-wzorów) takich jak ten. Ogólnie dokumentacja Androida jest znacznie bardziej rozproszona i mniej skoncentrowana. Rozumiem, że kryje się za tym kilka przyczyn, ale jest to długa dyskusja, więc jeśli czujesz się zmuszony do rozmowy na ten temat, daj mi znać.
DougW
6
Możesz łatwo napisać metodę statyczną, która tworzy LinearLayout z adaptera.
Romain Guy,
125
To NIE jest odpowiedź na How can I put a ListView into a ScrollView without it collapsing?... Możesz powiedzieć, że nie powinieneś tego robić, ale także dać odpowiedź, jak to zrobić, to dobra odpowiedź. Wiesz, że ListView ma inne fajne funkcje oprócz przewijania, funkcje, których nie ma LinearLayout.
Jorge Fuentes González
44
Co jeśli masz region zawierający ListView + inne widoki i chcesz przewijać wszystko jako jeden? To wydaje się rozsądnym przypadkiem użycia do umieszczenia ListView w ScrollView. Zamiana ListView na LinearLayout nie jest rozwiązaniem, ponieważ wtedy nie można używać adapterów.
Barry Fruitman,
262

Oto moje rozwiązanie. Jestem całkiem nowy na platformie Android i jestem pewien, że jest to trochę hack, szczególnie w części dotyczącej bezpośredniego wywoływania .measure i bezpośredniego ustawiania LayoutParams.heightwłaściwości, ale działa.

Wszystko, co musisz zrobić, to zadzwonić, Utility.setListViewHeightBasedOnChildren(yourListView)a zostanie on przeskalowany, aby dokładnie dopasować wysokość jego przedmiotów.

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);
    }
}
DougW
źródło
66
Właśnie odtworzyłeś bardzo drogi LinearLayout :)
Romain Guy
82
Z wyjątkiem tego, LinearLayoutże nie ma wszystkich nieodłącznych elementów ListView- nie ma dzielników, widoków nagłówka / stopki, selektora listy, który pasuje do kolorów motywu interfejsu telefonu itp. Szczerze mówiąc, nie ma powodu, dla którego nie można przewijać wewnętrzny pojemnik, aż dotrze do końca, a następnie przestanie przechwytywać zdarzenia dotykowe, aby zewnętrzny pojemnik przewinął się. Każda przeglądarka stacjonarna robi to, gdy używasz kółka przewijania. Sam Android może obsługiwać przewijanie zagnieżdżone, jeśli poruszasz się po trackballu, więc dlaczego nie za pomocą dotyku?
Neil Traft
29
listItem.measure(0,0)wyrzuci NPE, jeśli listItem jest instancją ViewGroup. Dodałem wcześniej listItem.measure:if (listItem instanceof ViewGroup) listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
Siklab.ph
4
Naprawiono wyświetlanie list z wypełnieniem innym niż 0:int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
Paul
8
Niestety ta metoda jest nieco wadliwa, gdy ListViewmoże zawierać elementy o zmiennej wysokości. Wezwanie do getMeasuredHeight()zwrócenia tylko docelowej minimalnej wysokości w przeciwieństwie do rzeczywistej wysokości. Próbowałem użyć getHeight()zamiast tego, ale zwraca 0. Czy ktoś ma wgląd w to, jak do tego podejść?
Matt Huggins,
89

To na pewno zadziała ............
Musisz po prostu zastąpić swój <ScrollView ></ScrollView>plik XML w układzie tym Custom ScrollViewpodobnym <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;
    }
}
Atul Bhardwaj
źródło
4
To jest bardzo dobre. Umożliwia także umieszczenie fragmentu Map Google wewnątrz ScrollView i nadal działa powiększanie / pomniejszanie. Dzięki.
Magnus Johansson,
Czy ktoś zauważył wady tego podejścia? Obawiam się, że to takie proste: o
Marija Milosevic
1
Ładne rozwiązanie, powinna być zaakceptowana odpowiedź +1! Wymieszałem to z odpowiedzią poniżej (od djunod) i to działa świetnie!
tanou
1
To jedyna poprawka, która powoduje, że przewijanie i klikanie dzieci pracuje dla mnie w tym samym czasie. Wielkie dzięki
pellyadolfo,
25

Zamiast wstawić do ListViewśrodka ScrollView, możemy użyć ListViewjako ScrollView. Rzeczy, które muszą być w ListViewśrodku, można umieścić w środku ListView. Inne układy na górze i na dole ListViewmożna umieścić, dodając układy do nagłówka i stopki ListView. Całość ListViewda ci przewijanie.

Bieg
źródło
3
To najbardziej elegancka i odpowiednia odpowiedź imho. Aby uzyskać więcej informacji na temat tego podejścia, zobacz tę odpowiedź: stackoverflow.com/a/20475821/1221537
adamdport
21

Istnieje wiele sytuacji, w których sensowne jest posiadanie ListView w ScrollView.

Oto kod oparty na sugestii DougW ... działa we fragmencie, zajmuje mniej pamięci.

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

wywołanie setListViewHeightBasedOnChildren (widok listy) na każdym osadzonym widoku listy.

djunod
źródło
Nie działa, gdy elementy listy mają zmienne rozmiary, tj. Niektóre widoki tekstu, które rozwijają się w wielu wierszach. Jakieś rozwiązanie?
Khobaib
Sprawdź mój komentarz tutaj - daje idealne rozwiązanie - stackoverflow.com/a/23315696/1433187
Khobaib
pracował dla mnie z API 19, ale nie z API 23: tutaj dodaje dużo białych znaków pod listView.
Lucker10,
17

ListView może już mierzyć swoją wysokość, aby wyświetlić wszystkie elementy, ale nie robi tego, gdy po prostu określisz wrap_content (MeasureSpec.UNSPECIFIED). Zrobi to, gdy otrzyma wysokość z MeasureSpec.AT_MOST. Dzięki tej wiedzy możesz utworzyć bardzo prostą podklasę, aby rozwiązać ten problem, który działa o wiele lepiej niż którekolwiek z powyższych rozwiązań. Nadal powinieneś używać wrap_content z tą podklasą.

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

Manipulowanie heightMeasureSpec o wartości AT_MOST o bardzo dużym rozmiarze (Integer.MAX_VALUE >> 4) powoduje, że ListView mierzy wszystkie swoje potomne do podanej (bardzo dużej) wysokości i odpowiednio ustawia wysokość.

Działa to lepiej niż inne rozwiązania z kilku powodów:

  1. Mierzy wszystko poprawnie (wypełnienie, przekładki)
  2. Mierzy ListView podczas przejścia miary
  3. Ze względu na # 2, poprawnie obsługuje zmiany szerokości lub liczby elementów bez dodatkowego kodu

Z drugiej strony, można argumentować, że robienie tego polega na nieudokumentowanym zachowaniu w zestawie SDK, które może ulec zmianie. Z drugiej strony można argumentować, że tak właśnie wrap_content powinien naprawdę współpracować z ListView i że bieżące zachowanie wrap_content jest po prostu zepsute.

Jeśli obawiasz się, że zachowanie może się zmienić w przyszłości, powinieneś po prostu skopiować funkcję onMeasure i powiązane funkcje z ListView.java do swojej własnej podklasy, a następnie wykonać ścieżkę AT_MOST przez uruchomienie onMeasure również dla NIEPECYFIKOWANEJ.

Nawiasem mówiąc, uważam, że jest to całkowicie poprawne podejście, gdy pracujesz z małą liczbą elementów listy. Może być nieefektywny w porównaniu do LinearLayout, ale gdy liczba elementów jest niewielka, użycie LinearLayout jest niepotrzebną optymalizacją, a zatem niepotrzebną złożonością.

Jason Y
źródło
Działa również z elementami o zmiennej wysokości w widoku listy!
Denny,
@Jason Y co to oznacza, Integer.MAX_VALUE >> 4 oznacza? co tam 4?
KJEjava48,
@Jason Y dla mnie zadziałało dopiero po zmianie Integer.MAX_VALUE >> 2 na Integer.MAX_VALUE >> 21. Dlaczego tak jest? Działa również ze ScrollView, a nie z NestedScrollView.
KJEjava48,
13

Jest do tego wbudowane ustawienie. W ScrollView:

android:fillViewport="true"

W Javie

mScrollView.setFillViewport(true);

Romain Guy wyjaśnia to dogłębnie tutaj: http://www.curious-creature.org/2010/08/15/scrollviews-handy-trick/

TalkLittle
źródło
2
To działa na LinearLayout, jak pokazuje. To nie działa dla ListView. W oparciu o datę tego posta wyobrażam sobie, że napisał go, aby podać przykład swojej alternatywy, ale to nie odpowiada na pytanie.
DougW
to szybkie rozwiązanie
10

Tworzysz niestandardowy widok listy, którego nie można przewijać

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

W twoim pliku zasobów układu

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

W pliku Java

Utwórz obiekt swojego customListview zamiast ListView, na przykład: NonScrollListView non_scroll_list = (NonScrollListView) findViewById (R.id.lv_nonscroll_list);

Dedaniya HirenKumar
źródło
8

Nie moglibyśmy użyć dwóch przewijanych jednocześnie. Otrzymamy całkowitą długość ListView i rozwiniemy listview o całkowitą wysokość. Następnie możemy dodać ListView w ScrollView bezpośrednio lub za pomocą LinearLayout, ponieważ ScrollView ma bezpośrednio jedno dziecko. skopiuj metodę setListViewHeightBasedOnChildren (lv) do swojego kodu i rozwiń widok listy, a następnie możesz użyć widoku listy w widoku przewijania. plik \ layout 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>

Metoda onCreate w klasie 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);
      }
Ashish Saini
źródło
Dobra poprawka! Działa w razie potrzeby. Dzięki. W moim przypadku mam całą listę widoków przed listą, a idealnym rozwiązaniem jest mieć tylko jeden pionowy przewijany widok.
Proverbio,
4

Jest to kombinacja odpowiedzi DougW, Good Guy Greg i Paul. Okazało się, że wszystko to było potrzebne, gdy próbowałem użyć tego z niestandardowym adapterem widoku listy i niestandardowymi elementami listy, w przeciwnym razie widok listy spowodował awarię aplikacji (również zawiesił się z odpowiedzią 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);
    }
Opuszczony wózek
źródło
Gdzie nazwać tę metodę?
Dhrupal
Po utworzeniu widoku listy i ustawieniu adaptera.
Opuszczony
Na długich listach robi się to zbyt wolno.
cakan
@cakan Zostało również napisane 2 lata wcześniej jako obejście, które pozwala na używanie razem niektórych składników interfejsu użytkownika, które tak naprawdę nie powinny być łączone. Dodajesz niepotrzebną ilość narzutu we współczesnym Androidzie, więc należy się spodziewać, że lista spowolni się, gdy stanie się duża.
Opuszczony
3

Nie należy umieszczać ListView w ScrollView, ponieważ ListView już jest ScrollView. To byłoby jak umieszczenie ScrollView w ScrollView.

Co próbujesz osiągnąć

Cheryl Simon
źródło
4
Usiłuję osiągnąć ListView wewnątrz ScrollView. Nie chcę, aby moja ListView była przewijalna, ale nie ma prostej właściwości, aby ustawić myListView.scrollEnabled = false;
DougW
Jak powiedział Romain Guy, nie jest to celem ListView. ListView to widok zoptymalizowany do wyświetlania długiej listy elementów, z mnóstwem skomplikowanej logiki do leniwego ładowania widoków, ponownego wykorzystania widoków itp. Nie ma to sensu, jeśli mówisz ListView, aby się nie przewijał. Jeśli chcesz tylko kilka elementów w kolumnie pionowej, użyj LinearLayout.
Cheryl Simon
9
Problem polega na tym, że istnieje wiele rozwiązań ListView, w tym jedno, o którym wspomniałeś. Wiele funkcji, które adaptery upraszczają, dotyczy jednak zarządzania danymi i ich kontroli, a nie optymalizacji interfejsu użytkownika. IMO powinno być prostym ListView i bardziej złożonym, który ponownie wykorzystuje wszystkie elementy listy i takie tam.
DougW
2
Absolutnie uzgodnione. Zauważ, że łatwo jest użyć istniejącego adaptera z LinearLayout, ale chcę wszystkich fajnych rzeczy, które zapewnia ListView, takich jak dzielniki, i że nie mam ochoty implementować ręcznie.
Artem Russakovskii
1
@ArtemRussakovskii Czy możesz wyjaśnić, jak łatwo zamienić ListView na LinearLayout na istniejący adapter?
happyhardik,
3

Przekształciłem @ DougW Utilityna C # (używane w Xamarinie). Poniższe działa dobrze dla elementów o stałej wysokości na liście i będzie w większości w porządku, a przynajmniej dobry początek, jeśli tylko niektóre elementy będą nieco większe niż element standardowy.

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

Dzięki @DougW, znalazłem się w trudnej sytuacji, kiedy musiałem pracować z Kodem Innych Ludzi. :-)

Phil Ryan
źródło
Jedno pytanie: kiedy wywołałeś setListViewHeightBasedOnChildren()metodę? Ponieważ nie działa to dla mnie cały czas.
Alexandre D.
@AlexandreD wywołujesz Utility.setListViewHeightBasedOnChildren (yourListView) po utworzeniu listy elementów. tzn. upewnij się, że najpierw utworzyłeś listę! Odkryłem, że robię listę, a nawet wyświetlałem swoje przedmioty w Console.WriteLine ... ale w jakiś sposób przedmioty były w niewłaściwym zakresie. Kiedy już to rozgryzłem i upewniłem się, że mam odpowiedni zakres dla mojej listy, zadziałało to jak urok.
Phil Ryan,
Faktem jest, że moje listy są tworzone w moim ViewModel. Jak mogę poczekać, aż moje listy zostaną utworzone w moim widoku przed wywołaniem funkcji Utility.setListViewHeightBasedOnChildren (yourListView)?
Alexandre D.,
3

To jedyna rzecz, która działała dla mnie:

na Lollipop możesz używać

yourtListView.setNestedScrollingEnabled(true);

Włącz lub wyłącz przewijanie zagnieżdżone dla tego widoku, jeśli potrzebujesz wstecznej zgodności ze starszą wersją systemu operacyjnego, będziesz musiał użyć RecyclerView.

JackA
źródło
Nie miało to żadnego wpływu na widoki mojej listy na API 22 w DialogFragment w działaniu AppCompat. Wciąż się zawalają. @Phil Ryan „s odpowiedź pracował przy odrobinie modyfikacji.
Hakan Çelik MK1
3

Wcześniej nie było to możliwe. Ale dzięki wydaniu nowych bibliotek Appcompat i bibliotek projektu można to osiągnąć.

Musisz tylko użyć NestedScrollView https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html

Nie wiem, czy będzie działać z Listview, czy nie, ale działa z RecyclerView.

Fragment kodu:

<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>
Shashank Kapsime
źródło
Fajnie, nie próbowałem tego, więc nie mogę potwierdzić, że wewnętrzny ListView się nie zwija, ale wygląda na to, że taka jest intencja. Głęboko zasmucony, że nie widzi Romaina Guy'a w dzienniku winy;)
DougW
1
Działa to z recyclinglerView, ale nie listView. Thanx
IgniteCoders
2

hej miałem podobny problem. Chciałem wyświetlić widok listy, który się nie przewijał, i stwierdziłem, że manipulowanie parametrami działało, ale było nieefektywne i zachowywałoby się inaczej na różnych urządzeniach. W rezultacie jest to fragment mojego kodu harmonogramu, który faktycznie robi to bardzo skutecznie .

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

Uwaga: jeśli go użyjesz, notifyDataSetChanged();nie będzie działać zgodnie z przeznaczeniem, ponieważ widoki nie zostaną przerysowane. Zrób to, jeśli potrzebujesz obejść

adapter.registerDataSetObserver(new DataSetObserver() {

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

            }

        });
PSchuette
źródło
2

Istnieją dwa problemy podczas korzystania z ListView wewnątrz ScrollView.

1- ListView musi całkowicie rozwinąć się do wysokości swoich dzieci. ten ListView rozwiązuje to:

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

Wysokość dzielnika musi wynosić 0, zamiast tego użyj dopełnienia w rzędach.

2- ListView zużywa zdarzenia dotykowe, więc ScrollView nie może być przewijany jak zwykle. Ten ScrollView rozwiązuje ten problem:

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

To najlepszy sposób, w jaki znalazłem sposób na załatwienie sprawy!

Ali
źródło
2

Rozwiązaniem, którego używam jest dodanie całej zawartości ScrollView (co powinno być powyżej i pod listView) jakoViewView i FooterView w ListView.

Działa to tak, że konwersja konwersji jest sprawdzana tak, jak powinna być.

brokedid
źródło
1

dzięki kodowi Vinaya tutaj jest mój kod, gdy nie możesz mieć widoku listy w widoku przewijania, ale potrzebujesz czegoś takiego

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

relativeLayout pozostaje w ScrollView, więc wszystko staje się przewijalne :)

max4ever
źródło
1

Oto mała modyfikacja na @djunod jest odpowiedź , że muszę zrobić to działa perfekcyjnie:

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();
}
Eng.Fouad
źródło
: Cześć, twoja odpowiedź działa przez większość czasu, ale nie działa, jeśli widok listy ma widok nagłówka i widok stopy. Czy możesz to sprawdzić?
hguser
Potrzebuję rozwiązania, gdy elementy listy mają zmienne rozmiary, tj. Niektóre widoki tekstu, które rozwijają się w wielu wierszach. Jakieś sugestie?
Khobaib
1

spróbuj tego, to działa dla mnie, zapomniałem, gdzie to znalazłem, gdzieś w przepełnieniu stosu, nie jestem tutaj, aby wyjaśnić, dlaczego to nie działa, ale to jest odpowiedź :).

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

EDYCJA! W końcu dowiedziałem się, skąd mam kod. tutaj! : ListView wewnątrz ScrollView nie przewija się na Androidzie

Bhimbim
źródło
Obejmuje to sposób zapobiegania utracie interakcji z widokiem przewijania, ale pytanie dotyczyło utrzymania wysokości widoku listy.
Opuszczony
1

Chociaż sugerowane metody setListViewHeightBasedOnChildren () działają w większości przypadków, w niektórych przypadkach, szczególnie z wieloma elementami, zauważyłem, że ostatnie elementy nie są wyświetlane. Dlatego postanowiłem naśladować prostą wersję zachowania ListView w celu ponownego użycia dowolnego kodu adaptera, oto alternatywa 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);

        }
    }
}
}
Alécio Carvalho
źródło
1

Wszystkie te odpowiedzi są błędne !!! Jeśli próbujesz umieścić widok listy w widoku przewijania, powinieneś przemyśleć swój projekt. Próbujesz umieścić ScrollView w ScrollView. Ingerowanie w listę obniży wydajność listy. Tak właśnie zaprojektował Android.

Jeśli naprawdę chcesz, aby lista znajdowała się w tym samym przewijaniu co inne elementy, wszystko, co musisz zrobić, to dodać pozostałe elementy na górę listy za pomocą prostej instrukcji switch w adapterze:

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


}

Adapter listy może obsłużyć wszystko, ponieważ renderuje tylko to, co jest widoczne.

TacoEater
źródło
0

Ten cały problem zniknąłby, gdyby LinearLayout miał metodę setAdapter, ponieważ wtedy, gdy ktoś powiedziałby komuś, aby go używał, alternatywa byłaby trywialna.

Jeśli rzeczywiście chcesz przewijać ListView w innym przewijanym widoku, to nie pomoże, ale w przeciwnym razie da ci to przynajmniej pomysł.

Musisz utworzyć niestandardowy adapter, aby połączyć całą zawartość, którą chcesz przewinąć i ustawić na to adapter ListView.

Nie mam pod ręką przykładowego kodu, ale jeśli chcesz czegoś takiego.

<ListView/>

(other content)

<ListView/>

Następnie musisz utworzyć adapter, który reprezentuje całą tę zawartość. ListView / Adaptery są wystarczająco inteligentne, aby obsługiwać różne typy, ale musisz sam napisać adapter.

Interfejs API interfejsu użytkownika Androida nie jest tak dojrzały jak prawie wszystko inne, więc nie ma takich samych subtelności jak inne platformy. Ponadto, robiąc coś na Androidzie, musisz być w trybie Android (unix), w którym oczekujesz, że aby zrobić cokolwiek, prawdopodobnie będziesz musiał zebrać funkcjonalność mniejszych części i napisać kilka własnych kodów, aby to zrobić praca.

majinnaibu
źródło
0

Kiedy umieszczamy w ListViewśrodku ScrollView, powstają dwa problemy. Jeden ScrollViewmierzy swoje dzieci w trybie UNSPECIFIED, więc ListViewustawia własną wysokość, aby pomieścić tylko jeden element (nie wiem dlaczego), drugi mierzy ScrollViewzdarzenie dotykowe, więc ListViewsię nie przewija.

Ale można umieścić ListViewwewnątrz ScrollViewz jakiegoś obejścia. Ten post przeze mnie wyjaśnia to obejście. Dzięki temu obejściu możemy również zachować ListViewfunkcję recyklingu.

Durgadass S.
źródło
0

Zamiast umieszczać widok listy w Widoku przewijania, umieść resztę treści między widokiem listy a otwarciem Widoku przewijania jako osobny widok i ustaw ten widok jako nagłówek widoku listy. Tak więc w końcu skończysz tylko widok listy przejmujący Scroll.

Saksham
źródło
0

Ta biblioteka jest najłatwiejszym i najszybszym rozwiązaniem problemu.

Kashif Nazar
źródło
-1

Oto moja wersja kodu, która oblicza całkowitą wysokość widoku listy. Ten działa dla mnie:

   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();
}
myforum
źródło