Кастомизация ListView является обычным явлением при разработке Приложения под ОС Android. ListView (Список) позволяет отображать однотипную информацию в Вашем Приложении, например показывать товары с ценами и характеристиками, если вы разрабатываете Приложение для онлайн продаж. При этом являясь по сути рутинной операцией, тем не менее, некоторые вопросы доставляют начинающим разработчикам массу неудобств. Некоторые из этих неудобств и вопросов будут разобраны в данной статье.
Структура проекта
В данном уроке предлагается создать небольшой список с флагами стран, названием валют этих стран и аббревиатурой этих стран.
Всего для создания списка используется четыре страны: Россия, Южная Африка, Сингапур, Турция. Флаги этих стран находятся в папке drawable. Также присутствует xml файл для создания бэкграунда ListViewи флагов. Для хранения названий и аббревиатур используется файл strings.xml. Также используется отдельный xml файл для создания разметки элемента ListView.
В Проекте присутствует три класса:
- MainActivity
- MyListAdapter - который отвечает за передачу данных в форму элемента списка
- DataFlags - класс, который призван облегчить передачу данных в Адаптер ListView
Формирование разметки элемента ListView
Для создания приятного глазу списка в данном уроке использовались следующие файлы:
- list_item.xml
- flag.xml
- listview_selector.xml
- styles.xml
listview.xml
В данном файле приводится разметка непосредственно элемента listview.
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:background="@drawable/listview_selector"
- android:orientation="horizontal">
- <ImageView
- android:id="@+id/flagImage"
- android:src="@drawable/flag_russian"
- android:layout_width="48dp"
- android:layout_height="40dp"
- android:layout_marginStart="16dp"
- android:layout_marginLeft="16dp"
- android:layout_marginRight="8dp"
- android:layout_marginEnd="8dp"
- android:contentDescription="@string/flag"
- android:background="@drawable/flag"
- android:layout_gravity="center_vertical"/>
- <TextView
- android:id="@+id/abbreviation"
- android:layout_width="52dp"
- android:layout_height="30dp"
- android:textColor="@color/black"
- android:textSize="24sp"
- android:text="@string/russianRUB"
- android:layout_marginRight="6dp"
- android:layout_marginEnd="6dp"
- android:layout_gravity="center_vertical"/>
- <TextView
- android:id="@+id/name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textColor="@color/black"
- android:textSize="14sp"
- android:text="@string/russian"
- android:gravity="center"
- android:layout_gravity="center"
- android:singleLine="false"/>
- </LinearLayout>
flag.xml
В данном файле создан backgroud для ImageView, в котором будет отображаться флаг. Края подложки для флага скруглены. А также настроен цвет подложки.
- <?xml version="1.0" encoding="UTF-8"?>
- <shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/blue_grey_100" />
- <padding
- android:bottom="2dp"
- android:top="2dp"
- android:right="2dp"
- android:left="2dp"/>
- <corners android:radius="4dp"/>
- </shape>
listview_selector.xml
Данная разметка используется как background для элемента ListView. В данном файле настраивается цвет фона элемента в нормальном режиме и при нажатии на элемент списка.
- <?xml version="1.0" encoding="utf-8"?>
- <selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item
- android:state_selected="false"
- android:state_pressed="false"
- android:drawable="@color/white" />
- <item
- android:state_pressed="true"
- android:drawable="@color/blue_grey_100" />
- <item
- android:state_selected="true"
- android:state_pressed="false"
- android:drawable="@color/blue_grey_100" />
- </selector>
styles.xml
Файл стилей знаком всем, кто занимается разработкой под Android. В данном случае он используется только лишь для хранения кодов цветов, используемых в данном примере.
- <resources>
- <!-- Base application theme. -->
- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
- </style>
- <color name="white">#ffffff</color>
- <color name="black">#000000</color>
- <color name="blue_grey_100">#CFD8DC</color>
- </resources>
activity_main.xml
Разметка основной активити, где будет располагаться наш кастомный ListView
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/white"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
- <ListView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/listViews" />
- </RelativeLayout>
Основной класс проекта - MainActivity.java
В Данном классе производится запуск основной активити проекта, в которой будет отображаться кастомный ListView. Данные в ListView передаются с помощью списка ArrayList, в который помечаются объекты данных для каждого элемента. Группировка данных для элемента ListView производится с помощью отдельного класса, формирующего структуру данных, в которой будут храниться ID изображения флага, ID строки названия валюты страны, а также ID строки Аббревиатур валют стран.
По сути в этом классе производится стандартная настройка ListView с тем отличием, что используется не обычный List, а ArrayList, в который передается объект, а не единичная строка.
- package ru.evileg.listview;
- import android.os.Bundle;
- import android.support.v7.app.ActionBarActivity;
- import android.view.Menu;
- import android.view.MenuItem;
- import android.widget.ListView;
- import java.util.ArrayList;
- public class MainActivity extends ActionBarActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- // Находим ListView в разметке активити
- ListView listView = (ListView) this.findViewById(R.id.listViews);
- // Создаём ListView, в котором будут храниться данные для списка
- ArrayList<DataFlags> list = new ArrayList<>();
- list.clear();
- /* Наполняем List данным. В данном случае данные передаются Объектом,
- * в котором хранятся id, указывающие на флаги в ресурсах, а также
- * на Строки, в которых прописаны названия валют стран и аббревиатуры этих валют */
- list.add(new DataFlags(R.drawable.flag_russian, R.string.russian, R.string.russianRUB));
- list.add(new DataFlags(R.drawable.flag_africa, R.string.africa, R.string.africaZAR));
- list.add(new DataFlags(R.drawable.flag_singapore, R.string.singapore, R.string.singaporeSGD));
- list.add(new DataFlags(R.drawable.flag_turkish, R.string.turkish, R.string.turkishTRY));
- /* Создаём адаптер, который будет передавать данные из List`а в
- * элемент ListView и подключаем его непосредственно к ListView`у
- */
- MyListAdapter myListAdaptery = new MyListAdapter(this, list);
- listView.setAdapter(myListAdaptery);
- }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.menu_main, menu);
- return true;
- }
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle action bar item clicks here. The action bar will
- // automatically handle clicks on the Home/Up button, so long
- // as you specify a parent activity in AndroidManifest.xml.
- int id = item.getItemId();
- //noinspection SimplifiableIfStatement
- if (id == R.id.action_settings) {
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
- }
Все строки, которые применяются для ListView хранятся непосредственно в файле ресурсов strings.xml
- <resources>
- <string name="app_name">ListView</string>
- <string name="action_settings">Settings</string>
- <string name="africaZAR">ZAR</string>
- <string name="russianRUB">RUB</string>
- <string name="singaporeSGD">SGD</string>
- <string name="turkishTRY">TRY</string>
- <string name="africa">South African\nrand</string>
- <string name="russian">Russian\nrouble</string>
- <string name="singapore">Singapore\ndollar</string>
- <string name="turkish">Turkish\nlira</string>
- <string name="flag">flag</string>
- </resources>
Класс DataFlags.java
Данный класс служит для формирования объекта, в котором хранятся данные для элементов ListView. Каждый объект обслуживает один из элементов ListView в данном проекте. Класс представляет собой общность переменных, и набор get методов для получения данных из объекта, формируемого данным классом.
- package ru.evileg.listview;
- /**
- * Application ListView
- * Created by EvILeg on 21.07.2015.
- */
- public class DataFlags {
- private int flagID;
- private int nameID;
- private int abbreviationID;
- public DataFlags (int _flagID, int _nameID, int _abbreviationID){
- flagID = _flagID;
- nameID = _nameID;
- abbreviationID = _abbreviationID;
- }
- public int getFlagID(){
- return flagID;
- }
- public int getNameID(){
- return nameID;
- }
- public int getAbbreviationID() {
- return abbreviationID;
- }
- }
Класс MyListAdapter
Самый важный класс в данном уроке. Именно этот класс служит для передачи данных из ArrayList в ListView. Данный класс расширяет возможности родительского класса BaseAdapter.
- package ru.evileg.listview;
- import android.content.Context;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.ImageView;
- import android.widget.TextView;
- import java.util.ArrayList;
- /**
- * Application ListView
- * Created by EvILeg on 21.07.2015.
- */
- public class MyListAdapter extends BaseAdapter {
- /*
- * Создаем объекты для отображения внешнего вида элемента
- * и объекта списка, с которым будет производиться работа
- */
- private LayoutInflater LInflater;
- private ArrayList<DataFlags> list;
- /*
- * Конструктор класса. В данном случае лишь транслируется лист с данными
- * в лист адаптера, с которым будет производиться непосредственная работа
- */
- public MyListAdapter(Context context, ArrayList<DataFlags> data){
- list = data;
- LInflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
- /*
- * Далее идут стандартные методы родительского класса BaseAdapter.
- * Внимательно ознакомьтесь с отличиями методов в уроке и методов,
- * которые автоматически создает Android Studio.
- * Данные методы должны работать непосредственно с используемым нами ArrayList
- * и структурой данных, формируемой классом DataFlags
- */
- @Override
- public int getCount() {
- return list.size();
- }
- @Override
- public DataFlags getItem(int position) {
- return list.get(position);
- }
- @Override
- public long getItemId(int position) {
- return position;
- }
- /*
- * Метод, в котором формируется внешний вид элементов с его наполнением
- */
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ViewHolder holder;
- View v = convertView;
- /*
- * В том случае, если вид элемента не создан, производится его создание
- * с помощью ViewHolder и тегирование данного элемента конкретным holder объектом
- */
- if ( v == null){
- holder = new ViewHolder();
- v = LInflater.inflate(R.layout.list_item, parent, false);
- holder.flag = (ImageView) v.findViewById(R.id.flagImage);
- holder.name = (TextView) v.findViewById(R.id.name);
- holder.abbreviation = ((TextView) v.findViewById(R.id.abbreviation));
- v.setTag(holder);
- }
- /*
- * После того, как все элементы определены, производится соотнесение
- * внешнего вида, данных и конкретной позиции в ListView.
- * После чего из ArrayList забираются данные для элемента ListView и
- * передаются во внешний вид элемента
- */
- holder = (ViewHolder) v.getTag();
- DataFlags dataFlags = getData(position);
- holder.flag.setImageResource(dataFlags.getFlagID());
- holder.name.setText(v.getResources().getString(dataFlags.getNameID()));
- holder.abbreviation.setText(v.getResources().getString(dataFlags.getAbbreviationID()));
- return v;
- }
- /*
- * Метод, который забирает объект из ArrayList для дальнейшей работы с ним
- * и передачи его данных в элемент ListView
- */
- DataFlags getData(int position){
- return (getItem(position));
- }
- /*
- * Данная структура данных необходима для того, чтобы при пролистывании
- * большого списка не возникало артефактов и перескакивания данных с одной позиции ListView
- * на другую, что достигается тегированием каждого элемента ListView
- */
- private static class ViewHolder {
- private ImageView flag;
- private TextView name;
- private TextView abbreviation;
- }
- }
Использование ViewHolder принципиально важно, поскольку, если не применять данный прием, можно получить неадекватной поведение ListView, при котором будет происходит неконтролируемое перескакивание данных с одной позиции ListView на другую. Причем это будет происходит только внешне, а на самом деле данные будут оставаться на своих местах. Будет возникать несоответствие внешнего вида ListView с его содержанием.
Итог
Если в процессе изучения урока не возникло никаких проблем и ошибок, то внешний вид полученного ListView должен быть подобен следующему:
Кастомный ListView
Кастомный ListView с нажатым элементом
Спасибо за пример.
У меня возникает ошибка.
AndroidRuntime: FATAL EXCEPTION: main
Подскажите, пожалуйста, с чем это может быть связано?
Честно, вот в этой ошибке может быть что угодно, вплоть до того, что вы просто где-то не в том порядке код запсали.
Это просто ошибка в рантайме, для всего, что могло случиться.