Evgenii Legotckoi
21 июля 2015 г. 19:55

Android. Кастомизация ListView

Кастомизация ListView является обычным явлением при разработке Приложения под ОС Android. ListView (Список) позволяет отображать однотипную информацию в Вашем Приложении, например показывать товары с ценами и характеристиками, если вы разрабатываете Приложение для онлайн продаж. При этом являясь по сути рутинной операцией, тем не менее, некоторые вопросы доставляют начинающим разработчикам массу неудобств. Некоторые из этих неудобств и вопросов будут разобраны в данной статье.

Структура проекта

В данном уроке предлагается создать небольшой список с флагами стран, названием валют этих стран и аббревиатурой этих стран.

Всего для создания списка используется четыре страны: Россия, Южная Африка, Сингапур, Турция. Флаги этих стран находятся в папке drawable. Также присутствует xml файл для создания бэкграунда ListViewи флагов. Для хранения названий и аббревиатур используется файл strings.xml. Также используется отдельный xml файл для создания разметки элемента ListView.

В Проекте присутствует три класса:

  1. MainActivity
  2. MyListAdapter - который отвечает за передачу данных в форму элемента списка
  3. DataFlags - класс, который призван облегчить передачу данных в Адаптер ListView

Формирование разметки элемента ListView

Для создания приятного глазу списка в данном уроке использовались следующие файлы:

  1. list_item.xml
  2. flag.xml
  3. listview_selector.xml
  4. styles.xml

listview.xml

В данном файле приводится разметка непосредственно элемента listview.

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="48dp"
  5. android:background="@drawable/listview_selector"
  6. android:orientation="horizontal">
  7.  
  8. <ImageView
  9. android:id="@+id/flagImage"
  10. android:src="@drawable/flag_russian"
  11. android:layout_width="48dp"
  12. android:layout_height="40dp"
  13. android:layout_marginStart="16dp"
  14. android:layout_marginLeft="16dp"
  15. android:layout_marginRight="8dp"
  16. android:layout_marginEnd="8dp"
  17. android:contentDescription="@string/flag"
  18. android:background="@drawable/flag"
  19. android:layout_gravity="center_vertical"/>
  20.  
  21. <TextView
  22. android:id="@+id/abbreviation"
  23. android:layout_width="52dp"
  24. android:layout_height="30dp"
  25. android:textColor="@color/black"
  26. android:textSize="24sp"
  27. android:text="@string/russianRUB"
  28. android:layout_marginRight="6dp"
  29. android:layout_marginEnd="6dp"
  30. android:layout_gravity="center_vertical"/>
  31.  
  32. <TextView
  33. android:id="@+id/name"
  34. android:layout_width="match_parent"
  35. android:layout_height="wrap_content"
  36. android:textColor="@color/black"
  37. android:textSize="14sp"
  38. android:text="@string/russian"
  39. android:gravity="center"
  40. android:layout_gravity="center"
  41. android:singleLine="false"/>
  42.  
  43. </LinearLayout>

flag.xml

В данном файле создан backgroud для ImageView, в котором будет отображаться флаг. Края подложки для флага скруглены. А также настроен цвет подложки.

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android">
  3.  
  4. <solid android:color="@color/blue_grey_100" />
  5.  
  6. <padding
  7. android:bottom="2dp"
  8. android:top="2dp"
  9. android:right="2dp"
  10. android:left="2dp"/>
  11.  
  12. <corners android:radius="4dp"/>
  13.  
  14. </shape>

listview_selector.xml

Данная разметка используется как background для элемента ListView. В данном файле настраивается цвет фона элемента в нормальном режиме и при нажатии на элемент списка.

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3. <item
  4. android:state_selected="false"
  5. android:state_pressed="false"
  6. android:drawable="@color/white" />
  7.  
  8. <item
  9. android:state_pressed="true"
  10. android:drawable="@color/blue_grey_100" />
  11.  
  12. <item
  13. android:state_selected="true"
  14. android:state_pressed="false"
  15. android:drawable="@color/blue_grey_100" />
  16.  
  17. </selector>

styles.xml

Файл стилей знаком всем, кто занимается разработкой под Android. В данном случае он используется только лишь для хранения кодов цветов, используемых в данном примере.

  1. <resources>
  2.  
  3. <!-- Base application theme. -->
  4. <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
  5. </style>
  6.  
  7. <color name="white">#ffffff</color>
  8. <color name="black">#000000</color>
  9. <color name="blue_grey_100">#CFD8DC</color>
  10.  
  11. </resources>

activity_main.xml

Разметка основной активити, где будет располагаться наш кастомный ListView

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:background="@color/white"
  6. android:paddingLeft="@dimen/activity_horizontal_margin"
  7. android:paddingRight="@dimen/activity_horizontal_margin"
  8. android:paddingTop="@dimen/activity_vertical_margin"
  9. android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
  10.  
  11. <ListView
  12. android:layout_width="match_parent"
  13. android:layout_height="match_parent"
  14. android:id="@+id/listViews" />
  15.  
  16. </RelativeLayout>

Основной класс проекта - MainActivity.java

В Данном классе производится запуск основной активити проекта, в которой будет отображаться кастомный ListView. Данные в ListView передаются с помощью списка ArrayList, в который помечаются объекты данных для каждого элемента. Группировка данных для элемента ListView производится с помощью отдельного класса, формирующего структуру данных, в которой будут храниться ID изображения флага, ID строки названия валюты страны, а также ID строки Аббревиатур валют стран.

По сути в этом классе производится стандартная настройка ListView с тем отличием, что используется не обычный List, а ArrayList, в который передается объект, а не единичная строка.

  1. package ru.evileg.listview;
  2.  
  3. import android.os.Bundle;
  4. import android.support.v7.app.ActionBarActivity;
  5. import android.view.Menu;
  6. import android.view.MenuItem;
  7. import android.widget.ListView;
  8.  
  9. import java.util.ArrayList;
  10.  
  11.  
  12. public class MainActivity extends ActionBarActivity {
  13.  
  14. @Override
  15. protected void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. setContentView(R.layout.activity_main);
  18.  
  19. // Находим ListView в разметке активити
  20. ListView listView = (ListView) this.findViewById(R.id.listViews);
  21.  
  22. // Создаём ListView, в котором будут храниться данные для списка
  23. ArrayList<DataFlags> list = new ArrayList<>();
  24. list.clear();
  25.  
  26. /* Наполняем List данным. В данном случае данные передаются Объектом,
  27. * в котором хранятся id, указывающие на флаги в ресурсах, а также
  28. * на Строки, в которых прописаны названия валют стран и аббревиатуры этих валют */
  29. list.add(new DataFlags(R.drawable.flag_russian, R.string.russian, R.string.russianRUB));
  30. list.add(new DataFlags(R.drawable.flag_africa, R.string.africa, R.string.africaZAR));
  31. list.add(new DataFlags(R.drawable.flag_singapore, R.string.singapore, R.string.singaporeSGD));
  32. list.add(new DataFlags(R.drawable.flag_turkish, R.string.turkish, R.string.turkishTRY));
  33.  
  34. /* Создаём адаптер, который будет передавать данные из List`а в
  35. * элемент ListView и подключаем его непосредственно к ListView`у
  36. */
  37. MyListAdapter myListAdaptery = new MyListAdapter(this, list);
  38. listView.setAdapter(myListAdaptery);
  39. }
  40.  
  41. @Override
  42. public boolean onCreateOptionsMenu(Menu menu) {
  43. // Inflate the menu; this adds items to the action bar if it is present.
  44. getMenuInflater().inflate(R.menu.menu_main, menu);
  45. return true;
  46. }
  47.  
  48. @Override
  49. public boolean onOptionsItemSelected(MenuItem item) {
  50. // Handle action bar item clicks here. The action bar will
  51. // automatically handle clicks on the Home/Up button, so long
  52. // as you specify a parent activity in AndroidManifest.xml.
  53. int id = item.getItemId();
  54.  
  55. //noinspection SimplifiableIfStatement
  56. if (id == R.id.action_settings) {
  57. return true;
  58. }
  59.  
  60. return super.onOptionsItemSelected(item);
  61. }
  62. }

Все строки, которые применяются для ListView хранятся непосредственно в файле ресурсов strings.xml

  1. <resources>
  2. <string name="app_name">ListView</string>
  3. <string name="action_settings">Settings</string>
  4.  
  5. <string name="africaZAR">ZAR</string>
  6. <string name="russianRUB">RUB</string>
  7. <string name="singaporeSGD">SGD</string>
  8. <string name="turkishTRY">TRY</string>
  9.  
  10. <string name="africa">South African\nrand</string>
  11. <string name="russian">Russian\nrouble</string>
  12. <string name="singapore">Singapore\ndollar</string>
  13. <string name="turkish">Turkish\nlira</string>
  14.  
  15. <string name="flag">flag</string>
  16. </resources>

Класс DataFlags.java

Данный класс служит для формирования объекта, в котором хранятся данные для элементов ListView. Каждый объект обслуживает один из элементов ListView в данном проекте. Класс представляет собой общность переменных, и набор get методов для получения данных из объекта, формируемого данным классом.

  1. package ru.evileg.listview;
  2.  
  3. /**
  4. * Application ListView
  5. * Created by EvILeg on 21.07.2015.
  6. */
  7. public class DataFlags {
  8.  
  9. private int flagID;
  10. private int nameID;
  11. private int abbreviationID;
  12.  
  13. public DataFlags (int _flagID, int _nameID, int _abbreviationID){
  14. flagID = _flagID;
  15. nameID = _nameID;
  16. abbreviationID = _abbreviationID;
  17. }
  18.  
  19. public int getFlagID(){
  20. return flagID;
  21. }
  22.  
  23. public int getNameID(){
  24. return nameID;
  25. }
  26.  
  27. public int getAbbreviationID() {
  28. return abbreviationID;
  29. }
  30. }

Класс MyListAdapter

Самый важный класс в данном уроке. Именно этот класс служит для передачи данных из ArrayList в ListView. Данный класс расширяет возможности родительского класса BaseAdapter.

  1. package ru.evileg.listview;
  2.  
  3. import android.content.Context;
  4. import android.view.LayoutInflater;
  5. import android.view.View;
  6. import android.view.ViewGroup;
  7. import android.widget.BaseAdapter;
  8. import android.widget.ImageView;
  9. import android.widget.TextView;
  10.  
  11. import java.util.ArrayList;
  12.  
  13. /**
  14. * Application ListView
  15. * Created by EvILeg on 21.07.2015.
  16. */
  17. public class MyListAdapter extends BaseAdapter {
  18.  
  19. /*
  20. * Создаем объекты для отображения внешнего вида элемента
  21. * и объекта списка, с которым будет производиться работа
  22. */
  23. private LayoutInflater LInflater;
  24. private ArrayList<DataFlags> list;
  25.  
  26. /*
  27. * Конструктор класса. В данном случае лишь транслируется лист с данными
  28. * в лист адаптера, с которым будет производиться непосредственная работа
  29. */
  30. public MyListAdapter(Context context, ArrayList<DataFlags> data){
  31.  
  32. list = data;
  33. LInflater = (LayoutInflater) context
  34. .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  35. }
  36.  
  37. /*
  38. * Далее идут стандартные методы родительского класса BaseAdapter.
  39. * Внимательно ознакомьтесь с отличиями методов в уроке и методов,
  40. * которые автоматически создает Android Studio.
  41. * Данные методы должны работать непосредственно с используемым нами ArrayList
  42. * и структурой данных, формируемой классом DataFlags
  43. */
  44. @Override
  45. public int getCount() {
  46. return list.size();
  47. }
  48.  
  49. @Override
  50. public DataFlags getItem(int position) {
  51. return list.get(position);
  52. }
  53.  
  54. @Override
  55. public long getItemId(int position) {
  56. return position;
  57. }
  58.  
  59. /*
  60. * Метод, в котором формируется внешний вид элементов с его наполнением
  61. */
  62. @Override
  63. public View getView(int position, View convertView, ViewGroup parent) {
  64.  
  65. ViewHolder holder;
  66. View v = convertView;
  67.  
  68. /*
  69. * В том случае, если вид элемента не создан, производится его создание
  70. * с помощью ViewHolder и тегирование данного элемента конкретным holder объектом
  71. */
  72. if ( v == null){
  73. holder = new ViewHolder();
  74. v = LInflater.inflate(R.layout.list_item, parent, false);
  75. holder.flag = (ImageView) v.findViewById(R.id.flagImage);
  76. holder.name = (TextView) v.findViewById(R.id.name);
  77. holder.abbreviation = ((TextView) v.findViewById(R.id.abbreviation));
  78. v.setTag(holder);
  79. }
  80.  
  81. /*
  82. * После того, как все элементы определены, производится соотнесение
  83. * внешнего вида, данных и конкретной позиции в ListView.
  84. * После чего из ArrayList забираются данные для элемента ListView и
  85. * передаются во внешний вид элемента
  86. */
  87. holder = (ViewHolder) v.getTag();
  88. DataFlags dataFlags = getData(position);
  89.  
  90. holder.flag.setImageResource(dataFlags.getFlagID());
  91. holder.name.setText(v.getResources().getString(dataFlags.getNameID()));
  92. holder.abbreviation.setText(v.getResources().getString(dataFlags.getAbbreviationID()));
  93.  
  94. return v;
  95. }
  96.  
  97. /*
  98. * Метод, который забирает объект из ArrayList для дальнейшей работы с ним
  99. * и передачи его данных в элемент ListView
  100. */
  101. DataFlags getData(int position){
  102. return (getItem(position));
  103. }
  104.  
  105. /*
  106. * Данная структура данных необходима для того, чтобы при пролистывании
  107. * большого списка не возникало артефактов и перескакивания данных с одной позиции ListView
  108. * на другую, что достигается тегированием каждого элемента ListView
  109. */
  110. private static class ViewHolder {
  111. private ImageView flag;
  112. private TextView name;
  113. private TextView abbreviation;
  114. }
  115. }

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

Итог

Если в процессе изучения урока не возникло никаких проблем и ошибок, то внешний вид полученного ListView должен быть подобен следующему:

Кастомный ListView

Кастомный ListView с нажатым элементом

Рекомендуемые статьи по этой тематике

По статье задано1вопрос(ов)

2

Вам это нравится? Поделитесь в социальных сетях!

ДВ
  • 9 марта 2019 г. 17:53
  • (ред.)

Спасибо за пример.
У меня возникает ошибка.

AndroidRuntime: FATAL EXCEPTION: main
Подскажите, пожалуйста, с чем это может быть связано?

Честно, вот в этой ошибке может быть что угодно, вплоть до того, что вы просто где-то не в том порядке код запсали.

  1. AndroidRuntime: FATAL EXCEPTION: main

Это просто ошибка в рантайме, для всего, что могло случиться.

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь