Android ListView с различными макетами для каждой строки

348

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

w.donahue
источник
1
Обновление: демонстрация для многострочного
nitesh

Ответы:

413

Поскольку вы знаете, сколько типов макетов у вас будет - эти методы можно использовать.

getViewTypeCount() - этот метод возвращает информацию о количестве типов строк в вашем списке

getItemViewType(int position) - возвращает информацию, какой тип макета вы должны использовать на основе позиции

Тогда вы раздуваете макет, только если он нулевой, и определяете тип с помощью getItemViewType.

Посмотрите на этот учебник для получения дополнительной информации.

Чтобы добиться некоторой оптимизации структуры, которую вы описали в комментарии, я бы предложил:

  • Хранение просмотров в объекте называется ViewHolder. Это увеличит скорость, потому что вам не придется findViewById()каждый раз вызывать getViewметод. Смотрите List14 в демоверсиях API .
  • Создайте один общий макет, который будет соответствовать всем комбинациям свойств и скрыть некоторые элементы, если в текущей позиции его нет.

Я надеюсь, что это поможет вам. Если бы вы могли предоставить некоторую заглушку XML со своей структурой данных и информацию о том, как именно вы хотите отобразить ее в строку, я мог бы дать вам более точный совет. По пикселям.

Cristian
источник
Спасибо блогу было очень приятно, но я добавил флажок. У меня была проблема в том, что проверим первый элемент и прокручиваем список. Странно анонимные предметы, где проверяются. Можете ли вы предоставить решение для этого. Спасибо
Лалит Поптани
2
извините за то, что выкопал это снова, но вы бы порекомендовали иметь один большой файл макета и контролировать видимость его частей, вместо того, чтобы иметь отдельные файлы макета, которые раздуваются соответственно с использованием getItemViewType?
Макибо
2
Вы тоже можете это сделать. Хотя я все же предпочитаю способ разоблачения здесь. Это проясняет, чего вы хотите достичь.
Кристиан
Но в стратегии с несколькими макетами мы не можем должным образом использовать пользовательский держатель представления, поскольку setTag может содержать только один держатель представления, и всякий раз, когда макет строки переключается снова, нам нужно вызывать findViewById (). Что делает просмотр списка очень низкой производительностью. Я лично испытал это, что вы предлагаете на это?
pyus13
@ pyus13 вы можете объявить столько представлений, сколько вы хотите, в одном держателе представлений, и нет необходимости использовать все представления, объявленные в держателе представлений. Если вам нужен пример кода, пожалуйста, дайте мне знать, я опубликую его.
Ахмад Али Насир
62

Я знаю, как создать настраиваемый адаптер строки + настраиваемый массив для поддержки настраиваемой строки для всего списка. Но как один просмотр списка может поддерживать много разных стилей строк?

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

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 напрямую, а используете или наследуете один из их подклассов. Подклассы Adapter могут добавлять дополнительные функции, которые изменяют способ получения пользовательских макетов для разных строк. Поскольку представление, используемое для данной строки, управляется адаптером, уловка состоит в том, чтобы заставить адаптер возвращать желаемое представление для данной строки. Как это сделать, зависит от конкретного адаптера.

Например, чтобы использовать ArrayAdapter ,

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

Но использовать производные CursorAdapter ,

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

Тем не менее, чтобы использовать SimpleCursorAdapter ,

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

Посмотрите конкретные примеры / учебные пособия для типа адаптера, который вы в конечном итоге используете.

Берт F
источник
Любые мысли о том, какой из этих типов адаптеров лучше всего подходит для гибкой реализации адаптера? Я добавляю еще один вопрос на доске для этого.
Андройдер
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>

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>

Затем мы создаем элемент списка. В нашем случае со строкой и типом.

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

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>
Шив
источник
<include layout = "@ layout / content_main" /> откуда это
взято
Мне нужно было только предложение (convertView == null). Не нужен «зритель»
Jomme
14

В своем адаптере массива вы переопределяете метод getView (), как вы, вероятно, знакомы с ним. Затем все, что вам нужно сделать, это использовать оператор switch или оператор if, чтобы вернуть определенный пользовательский View в зависимости от аргумента позиции, переданного в метод getView. Android умен в том, что он даст вам convertView только соответствующего типа для вашей позиции / строки; Вам не нужно проверять, что это правильный тип. В этом вы можете помочь Android, переопределив методы getItemViewType () и getViewTypeCount () соответствующим образом.

Jems
источник
4

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

Пожалуйста, отметьте это для использования getViewTypeCount () и getItemViewType () в адаптере.

Ссылка: the-use-of-getviewtypecount

kyogs
источник
1

ListView был предназначен для простых случаев использования, таких как статическое представление для всех элементов строки.
Поскольку вам необходимо создавать ViewHolders, активно использовать getItemViewType()и динамически показывать различные XML-элементы макета элементов строки, попробуйте сделать это с помощью RecyclerView , который доступен в Android API 22. Он обеспечивает лучшую поддержку и структуру для нескольких типов представлений.

Прочтите это руководство о том, как использовать RecyclerView для того, что вы ищете.

Phileo99
источник