Android ListView z różnymi układami dla każdego wiersza

348

Próbuję ustalić najlepszy sposób, aby mieć pojedynczy ListView, który zawiera różne układy dla każdego wiersza. Wiem, jak utworzyć niestandardowy wiersz + niestandardowy adapter tablicy, aby obsługiwał niestandardowy wiersz dla całego widoku listy, ale jak mogę wdrożyć wiele różnych stylów wierszy w widoku listy?

w.donahue
źródło
1
aktualizacja: Demo dla układu wielu wierszy za pomocą RecyclerView systemu AndroidView code2concept.blogspot.in/2015/10/…
nitesh

Odpowiedzi:

413

Ponieważ wiesz, ile rodzajów układów miałbyś - możesz skorzystać z tych metod.

getViewTypeCount() - ta metoda zwraca informację, ile rodzajów wierszy masz na liście

getItemViewType(int position) - zwraca informację, jakiego typu układu należy użyć na podstawie pozycji

Następnie nadmuchujesz układ tylko wtedy, gdy jest on pusty i określasz typ za pomocą getItemViewType.

Więcej informacji znajdziesz w tym samouczku .

Aby osiągnąć pewne optymalizacje w strukturze, które opisałeś w komentarzu, sugerowałbym:

  • Przechowywanie widoków w obiekcie o nazwie ViewHolder. Zwiększy to prędkość, ponieważ nie będziesz musiał wywoływać za findViewById()każdym razem getViewmetody. Zobacz List14 w prezentacjach API .
  • Utwórz jeden ogólny układ, który będzie pasował do wszystkich kombinacji właściwości i ukryje niektóre elementy, jeśli bieżąca pozycja go nie ma.

Mam nadzieję, że ci to pomoże. Jeśli możesz podać kod XML ze swoją strukturą danych i informacjami, w jaki sposób chcesz zamapować go w wierszu, byłbym w stanie udzielić Ci dokładniejszych porad. Po pikselach.

Cristian
źródło
Dzięki blog był bardzo miły, ale dodałem pole wyboru. Miałem problem polegający na tym, że sprawdzi pierwszy element i przewinie listę. Dziwnie anonimowe przedmioty, które zostały sprawdzone. Czy możesz podać rozwiązanie tego problemu. Dzięki
Lalit Poptani,
2
przepraszam za ponowne wykopanie tego, ale w rzeczywistości zaleca się posiadanie jednego dużego pliku układu i kontrolowanie widoczności jego części, zamiast osobnych plików układu, które są odpowiednio zawyżane za pomocą getItemViewType?
Makibo
2
Też możesz to zrobić. Chociaż nadal wolę sposób, w jaki tutaj się ujawniłem. Wyjaśnia to, co chcesz osiągnąć.
Cristian
Ale w strategii wielu układów nie możemy poprawnie wyświetlić uchwytu widoku, ponieważ setTag może zawierać tylko jeden uchwyt widoku i za każdym razem, gdy układ wierszy zmienia się ponownie, musimy wywołać findViewById (). Co sprawia, że ​​widok listy jest bardzo niski. Osobiście doświadczyłem tego, co na to sugerujesz?
pyus13
@ pyus13 możesz zadeklarować dowolną liczbę widoków w jednym uchwycie widoku i nie jest konieczne używanie każdego widoku zadeklarowanego w uchwycie widoku. Jeśli potrzebny jest przykładowy kod, daj mi znać, opublikuję go.
Ahmad Ali Nasir
62

Wiem, jak utworzyć niestandardowy wiersz + niestandardowy adapter tablicy do obsługi niestandardowego wiersza dla całego widoku listy. Ale w jaki sposób jeden widok listy może obsługiwać wiele różnych stylów wierszy?

Znasz już podstawy. Wystarczy, że niestandardowy adapter zwróci inny układ / widok na podstawie dostarczonych informacji o wierszu / kursorze.

A ListViewmoże obsługiwać wiele stylów wierszy, ponieważ pochodzi z AdapterView :

AdapterView to widok, którego dzieci są określane przez Adapter.

Jeśli spojrzysz na adapter , zobaczysz metody uwzględniające użycie widoków specyficznych dla wierszy:

abstract int getViewTypeCount()
// Returns the number of types of Views that will be created ...

abstract int getItemViewType(int position)
// Get the type of View that will be created ...

abstract View getView(int position, View convertView, ViewGroup parent)
// Get a View that displays the data ...

Dwie ostatnie metody zapewniają położenie, dzięki czemu można użyć tego do określenia typu widoku, którego należy użyć dla tego wiersza .


Oczywiście na ogół nie używasz bezpośrednio AdapterView i Adapter, ale raczej używasz lub wywodzi się z jednej z ich podklas. Podklasy adaptera mogą dodawać dodatkowe funkcje, które zmieniają sposób uzyskiwania niestandardowych układów dla różnych wierszy. Ponieważ widok używany dla danego wiersza jest sterowany przez adapter, sztuczka polega na tym, aby adapter zwrócił żądany widok dla danego wiersza. Jak to zrobić, różni się w zależności od konkretnego adaptera.

Na przykład, aby użyć ArrayAdapter ,

  • przesłonić, getView()aby napompować, wypełnić i przywrócić żądany widok dla danej pozycji. getView()Metoda obejmuje możliwość ponownego wykorzystania poglądów za pośrednictwem convertViewparametru.

Ale aby użyć pochodnych CursorAdapter ,

  • przesłonić, newView()aby napompować, wypełnić i przywrócić żądany widok dla bieżącego stanu kursora (tj. bieżący „wiersz”) [należy również przesłonić bindView, aby widżet mógł ponownie wykorzystywać widoki]

Aby jednak użyć SimpleCursorAdapter ,

  • zdefiniuj SimpleCursorAdapter.ViewBinderza pomocą setViewValue()metody nadmuchiwania, wypełniania i zwracania pożądanego widoku dla danego wiersza (aktualny stan kursora) i „kolumny” danych. Metoda może zdefiniować tylko „specjalne” widoki i odroczyć standardowe zachowanie SimpleCursorAdapter dla „normalnych” powiązań.

Poszukaj konkretnych przykładów / samouczków dotyczących rodzaju używanego adaptera.

Bert F.
źródło
Czy są jakieś przemyślenia, który z tych typów adapterów jest najlepszy do elastycznej implementacji adaptera? W tym celu dodaję kolejne pytanie na tablicy.
Androider
1
@Androider - „najlepszy dla elastycznego” jest bardzo otwarty - nie ma klasy typu „wszystko dla wszystkich”, która zaspokoi każdą potrzebę; to bogata hierarchia - sprowadza się do tego, czy w podklasie znajduje się funkcjonalność przydatna w twoim celu. Jeśli tak, zacznij od tej podklasy; jeśli nie, przejdź do BaseAdapter. Pochodzenie z BaseAdapter byłoby najbardziej „elastyczne”, ale najgorzej byłoby w przypadku ponownego wykorzystania kodu i dojrzałości, ponieważ nie wykorzystuje on wiedzy i dojrzałości już wprowadzonych w innych adapterach. BaseAdapterjest dostępny w niestandardowych kontekstach, w których inne adaptery nie pasują.
Bert F
3
+1 za dokładne rozróżnienie między CursorAdapteri SimpleCursorAdapter.
Giulio Piancastelli,
1
zauważ również, że jeśli przesłonisz ArrayAdapter, nie ma znaczenia, jaki układ dajesz konstruktorowi, o ile getView()nadmuchuje i zwraca odpowiedni typ układu
woojoo666,
1
Należy zauważyć, że getViewTypeCount()jest uruchamiany tylko raz za każdym razem, gdy dzwonisz ListView.setAdapter(), a nie za każdym razem Adapter.notifyDataSetChanged().
hidro
43

Spójrz w poniższy kod.

Najpierw tworzymy niestandardowe układy. W tym przypadku cztery typy.

even.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ff500000"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/white"
        android:layout_width="match_parent"
        android:layout_gravity="center"
        android:textSize="24sp"
        android:layout_height="wrap_content" />

 </LinearLayout>

odd.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ff001f50"
    android:gravity="right"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/white"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:textSize="28sp"
        android:layout_height="wrap_content"  />

 </LinearLayout>

white.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ffffffff"
    android:gravity="right"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/black"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:textSize="28sp"
        android:layout_height="wrap_content"   />

 </LinearLayout>

black.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ff000000"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/white"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:textSize="33sp"
        android:layout_height="wrap_content"   />

 </LinearLayout>

Następnie tworzymy element widoku listy. W naszym przypadku ze sznurkiem i typem.

public class ListViewItem {
        private String text;
        private int type;

        public ListViewItem(String text, int type) {
            this.text = text;
            this.type = type;
        }

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public int getType() {
            return type;
        }

        public void setType(int type) {
            this.type = type;
        }

    }

Następnie tworzymy uchwyt widoku. Jest to zdecydowanie zalecane, ponieważ system operacyjny Android zachowuje odniesienie do układu, aby ponownie użyć elementu, gdy zniknie i pojawi się z powrotem na ekranie. Jeśli nie zastosujesz tego podejścia, za każdym razem, gdy element pojawi się na ekranie, system operacyjny Android utworzy nowy i spowoduje wyciek pamięci z aplikacji.

public class ViewHolder {
        TextView text;

        public ViewHolder(TextView text) {
            this.text = text;
        }

        public TextView getText() {
            return text;
        }

        public void setText(TextView text) {
            this.text = text;
        }

    }

Na koniec tworzymy nasz niestandardowy adapter nadpisujący getViewTypeCount () i getItemViewType (pozycja int).

public class CustomAdapter extends ArrayAdapter {

        public static final int TYPE_ODD = 0;
        public static final int TYPE_EVEN = 1;
        public static final int TYPE_WHITE = 2;
        public static final int TYPE_BLACK = 3;

        private ListViewItem[] objects;

        @Override
        public int getViewTypeCount() {
            return 4;
        }

        @Override
        public int getItemViewType(int position) {
            return objects[position].getType();
        }

        public CustomAdapter(Context context, int resource, ListViewItem[] objects) {
            super(context, resource, objects);
            this.objects = objects;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            ViewHolder viewHolder = null;
            ListViewItem listViewItem = objects[position];
            int listViewItemType = getItemViewType(position);


            if (convertView == null) {

                if (listViewItemType == TYPE_EVEN) {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_even, null);
                } else if (listViewItemType == TYPE_ODD) {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_odd, null);
                } else if (listViewItemType == TYPE_WHITE) {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_white, null);
                } else {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_black, null);
                }

                TextView textView = (TextView) convertView.findViewById(R.id.text);
                viewHolder = new ViewHolder(textView);

                convertView.setTag(viewHolder);

            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            viewHolder.getText().setText(listViewItem.getText());

            return convertView;
        }

    }

Nasza działalność jest mniej więcej taka:

private ListView listView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main); // here, you can create a single layout with a listview

        listView = (ListView) findViewById(R.id.listview);

        final ListViewItem[] items = new ListViewItem[40];

        for (int i = 0; i < items.length; i++) {
            if (i == 4) {
                items[i] = new ListViewItem("White " + i, CustomAdapter.TYPE_WHITE);
            } else if (i == 9) {
                items[i] = new ListViewItem("Black " + i, CustomAdapter.TYPE_BLACK);
            } else if (i % 2 == 0) {
                items[i] = new ListViewItem("EVEN " + i, CustomAdapter.TYPE_EVEN);
            } else {
                items[i] = new ListViewItem("ODD " + i, CustomAdapter.TYPE_ODD);
            }
        }

        CustomAdapter customAdapter = new CustomAdapter(this, R.id.text, items);
        listView.setAdapter(customAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView adapterView, View view, int i, long l) {
                Toast.makeText(getBaseContext(), items[i].getText(), Toast.LENGTH_SHORT).show();
            }
        });

    }
}

teraz utwórz widok listy w mainactivity.xml w ten sposób

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.example.shivnandan.gygy.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main" />

    <ListView
        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:id="@+id/listView"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"


        android:layout_marginTop="100dp" />

</android.support.design.widget.CoordinatorLayout>
Shiv
źródło
<include layout = "@ layout / content_main" /> skąd to pochodzi
nyxee
Potrzebowałem tylko klauzuli (convertView == null). Nie potrzebowałem „przeglądarki”
Jomme
14

W niestandardowym adapterze tablicowym zastępujesz metodę getView (), jak zapewne znasz. Następnie wystarczy użyć instrukcji switch lub instrukcji if, aby zwrócić określony widok niestandardowy w zależności od argumentu position przekazanego do metody getView. Android jest sprytny, ponieważ daje ci tylko widok konwersji odpowiedniego typu dla twojej pozycji / wiersza; nie musisz sprawdzać, czy jest poprawnego typu. Możesz w tym pomóc Androidowi, odpowiednio nadpisując metody getItemViewType () i getViewTypeCount ().

Jems
źródło
4

Jeśli musimy pokazać inny typ widoku w widoku listy, dobrze jest użyć getViewTypeCount () i getItemViewType () w adapterze zamiast przełączania widoku VIEW.GONE i VIEW.VISIBLE może być bardzo kosztownym zadaniem w getView (), które będzie wpływa na przewijanie listy.

Sprawdź to, aby użyć getViewTypeCount () i getItemViewType () w adapterze.

Link: the-use-of-getviewtypecount

kyogs
źródło
1

ListView był przeznaczony do prostych przypadków użycia, takich jak ten sam widok statyczny dla wszystkich elementów wiersza.
Ponieważ musisz tworzyć ViewHolders i w znacznym stopniu wykorzystywać getItemViewType()i dynamicznie wyświetlać różne pliki XML układu elementów wiersza, powinieneś spróbować to zrobić za pomocą RecyclerView , który jest dostępny w Android API 22. Oferuje lepszą obsługę i strukturę dla wielu typów widoków.

Sprawdź ten samouczek na temat korzystania z RecyclerView do robienia tego, czego szukasz.

Phileo99
źródło