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

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

Ответ 1

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

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

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

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

Посмотрите этот учебник.

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

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

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

Ответ 2

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

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

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

AdapterView представляет собой представление , чьи дочерние элементы определяются адаптером.

Если вы посмотрите на Adapter, вы увидите методы, которые учитывают использование представлений, специфичных для строки:

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,

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

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

Ответ 3

Взгляните на код ниже.

Сначала мы создаем пользовательские макеты. В этом случае четыре типа.

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>

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

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

    }
}

теперь создайте listview внутри 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>

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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

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