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

Вам це подобається? Поділіться в соціальних мережах!

ДВ
  • 09 березня 2019 р. 17:53
  • (відредаговано)

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

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

Evgenii Legotckoi
  • 11 березня 2019 р. 14:32

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

  1. AndroidRuntime: FATAL EXCEPTION: main

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
  • Останні коментарі
  • Evgenii Legotckoi
    16 квітня 2025 р. 17:08
    Благодарю за отзыв. И вам желаю всяческих успехов!
  • IscanderChe
    12 квітня 2025 р. 17:12
    Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
  • AK
    01 квітня 2025 р. 11:41
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    09 березня 2025 р. 21:02
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    09 березня 2025 р. 16:14
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…