Android ListView з різними макетами для кожного рядка


348

Я намагаюся визначити найкращий спосіб мати єдиний ListView, який містить різні макети для кожного рядка. Я знаю, як створити спеціальний рядок + адаптер спеціального масиву для підтримки користувацького рядка для всього перегляду списку, але як я можу реалізувати багато різних стилів рядків у ListView?


1
оновлення: демонстрація для макета декількох рядків за допомогою Android RecyclerView code2concept.blogspot.in/2015/10/…
nitesh

Відповіді:


413

Оскільки ви знаєте, скільки типів макетів у вас було б - можна використовувати ці методи.

getViewTypeCount() - цей метод повертає інформацію, скільки типів рядків у вашому списку

getItemViewType(int position) - повертає інформацію, тип макета ви повинні використовувати на основі позиції

Тоді ви надуваєте макет лише в тому випадку, якщо він є нульовим і визначаєте тип використання getItemViewType.

Перегляньте цей підручник для отримання додаткової інформації.

Щоб досягти деяких оптимізацій в структурі, які ви описали в коментарі, я б запропонував:

  • Збереження поглядів в назві об'єкта ViewHolder. Це збільшить швидкість, тому що вам не доведеться телефонувати findViewById()кожен раз у getViewспосіб. Див. Список14 в демонстраційних версіях API .
  • Створіть один загальний макет, який буде відповідати всім комбінаціям властивостей та приховати деякі елементи, якщо у поточному положенні його немає.

Я сподіваюся, що вам це допоможе. Якщо ви можете надати якийсь XML-заглушку зі своєю структурою даних та інформацією про те, як саме ви хочете зіставити їх у рядку, я зможу дати точнішу пораду. За пікселем.


Блог подяк був дуже приємний, але я додав прапорець. У мене виникла проблема з тим, щоб перевірити перший елемент і прокрутити список. Дивні анонімні предмети, де їх перевіряють. Чи можете ви надати рішення для цього. Спасибі
Лаліт Поптані

2
Вибачте, що викопали це ще раз, але ви б фактично рекомендували мати один великий файл макета та контролювати видимість його частин, а не окремі файли компонування, які завищуються відповідно за допомогою getItemViewType?
Макібо

2
Ви також можете це зробити. Хоча я все ще віддаю перевагу викритому тут. Це стає зрозумілішим, чого ви хочете досягти.
Крістіан

Але в декількох стратегіях макета ми не можемо належним чином власником перегляду користувачем, оскільки setTag може містити лише один власник перегляду, і кожен раз, коли макет рядка знову перемикається, нам потрібно викликати findViewById (). Що робить перегляд списку дуже низькою продуктивністю. Я особисто пережив це, що ви на це пропонуєте?
pyus13

@ pyus13 ви можете оголосити стільки переглядів, скільки вам потрібно, в одному власнику перегляду, і не потрібно використовувати кожен вид, заявлений у власнику представлення даних. Якщо потрібен зразок коду, будь ласка, повідомте мене, я опублікую його.
Ахмад Алі Насір

62

Я знаю, як створити спеціальний рядок + адаптер спеціального масиву для підтримки користувацького рядка для всього перегляду списку. Але як один перегляд списку може підтримувати багато різних стилів рядків?

Ви вже знаєте ази. Вам просто потрібно придбати свій спеціальний адаптер, щоб повернути інший макет / вид на основі наданої інформації про рядок / курсор.

A ListViewможе підтримувати кілька стилів рядків, оскільки він походить від AdapterView :

AdapterView - це вид , діти якого визначаються адаптером.

Якщо ви подивитесь на Адаптер , ви побачите методи, які враховують використання представлень для рядків:

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

Останні два методи забезпечують позицію, щоб ви могли використовувати її для визначення типу подання, який слід використовувати для цього рядка .


Звичайно, ви зазвичай не використовуєте AdapterView та Adapter безпосередньо, а скоріше використовуєте або походять з одного з їх підкласів. Підкласи адаптера можуть додавати додаткові функціональні можливості, які змінюють спосіб отримання користувацьких макетів для різних рядків. Оскільки перегляд, який використовується для даного рядка, керується адаптером, фокус полягає в тому, щоб змусити адаптер повернути потрібний вигляд для даного рядка. Як це зробити, відрізняється залежно від конкретного адаптера.

Наприклад, використовувати ArrayAdapter ,

  • замініть, getView()щоб надути, заповнити та повернути бажаний вигляд для заданої позиції. getView()Метод включає в себе можливість повторного використання поглядів через convertViewпараметр.

Але використовувати похідні CursorAdapter ,

  • замінити, newView()щоб надути, заповнити та повернути потрібний вигляд для поточного стану курсору (тобто поточного "рядка") [вам також потрібно переозначити, bindViewщоб віджет міг повторно використовувати представлення даних]

Однак для використання SimpleCursorAdapter ,

  • Визначимо SimpleCursorAdapter.ViewBinderз setViewValue()методом надути, заселити, і повернути бажаний вид для цього рядка (поточний стан курсору) і «стовпець» даних. Метод може визначити лише "спеціальні" погляди та відкласти стандартну поведінку SimpleCursorAdapter для "нормальних" прив'язок.

Знайдіть конкретні приклади / підручники для типу адаптера, який ви в кінцевому підсумку використовуєте.


Будь-які думки щодо того, який із цих типів адаптерів найкращий для гнучкої реалізації адаптера? Я додаю для цього ще одне запитання на дошці.
Androider

1
@Androider - "кращий для гнучких" дуже відкритий - не існує кінця, будь-який клас, який задовольнить кожну потребу; це багата ієрархія - вона зводиться до того, чи є функціональність у підкласі, який корисний для вашої мети. Якщо так, почніть з цього підкласу; якщо ні, рухайтесь до BaseAdapter. Виведення з BaseAdapter було б найбільш "гнучким", але було б найгіршим при повторному використанні та зрілості коду, оскільки воно не використовує переваги знань та зрілості, вже вкладених в інші адаптери. BaseAdapterє для нестандартних контекстів, де інші адаптери не вміщуються.
Берт Ф

3
+1 за точне розмежування між CursorAdapterта SimpleCursorAdapter.
Джуліо Піанкастеллі

1
також зауважте, що якщо ви переосмислите ArrayAdapter, не має значення, який макет ви надаєте конструктору, доки getView()надувається і повертається правильний тип макета
woojoo666

1
Слід зазначити, що getViewTypeCount()спрацьовує лише один раз, коли ви телефонуєте ListView.setAdapter(), не для кожного Adapter.notifyDataSetChanged().
гідро

43

Погляньте на код нижче.

Спочатку ми створюємо власні макети. У цьому випадку чотири типи.

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>

біла.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>

Потім ми створюємо елемент перегляду списку. У нашому випадку з рядком і типом.

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

    }

Після цього ми створюємо власник перегляду. Настійно рекомендується, оскільки ОС Android зберігає посилання на макет, щоб повторно використовувати предмет, коли він зникає і з'являється на екрані. Якщо ви не використовуєте цей підхід, кожен раз, коли ваш предмет з’явиться на екрані ОС Android, буде створено нове і призведе до того, що ваш додаток просочить пам'ять.

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

    }

Нарешті, ми створюємо наш спеціальний адаптер, що переосмислює getViewTypeCount () та getItemViewType (позиція 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;
        }

    }

І наша діяльність приблизно така:

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

    }
}

тепер створіть перегляд списку всередині mainactivity.xml, як це

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

<включити макет = "@ layout / content_main" /> звідки це
nyxee

Мені потрібен був лише пункт (convertView == null). Не знадобився "глядач"
Jomme

14

У вашому користувальницькому адаптері масиву ви переосмислюєте метод getView (), як вам, мабуть, знайомий. Тоді все, що вам потрібно зробити, - це використати оператор переключення або оператор if, щоб повернути певний спеціальний вид, залежно від аргументу позиції, переданого методу getView. Android розумний в тому, що він надасть лише конверсію перегляду відповідного типу для вашої позиції / рядка; вам не потрібно перевіряти, чи він правильного типу. Ви можете допомогти Android у цьому, замінивши методи getItemViewType () та getViewTypeCount () відповідним чином.


4

Якщо нам потрібно показати інший тип перегляду в перегляді списку, тоді добре використовувати getViewTypeCount () і getItemViewType () в адаптері замість перемикання перегляду VIEW.GONE і VIEW.VISIBLE може бути дуже дорогою задачею в getView (), яка буде впливати на прокрутку списку.

Перевірте це на використання getViewTypeCount () та getItemViewType () в адаптері.

Посилання: кількість-використання-getviewtypecount


1

ListView призначений для простих випадків використання, таких як однаковий статичний вигляд для всіх елементів рядка.
Оскільки вам доведеться створити ViewHolders та значно використати getItemViewType()та динамічно показувати різні розмітки елементів рядка xml, вам слід спробувати це зробити за допомогою RecyclerView , який доступний в Android API 22. Він пропонує кращу підтримку та структуру для декількох типів перегляду.

Перегляньте цей підручник про те, як використовувати RecyclerView, щоб робити те, що ви шукаєте.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.