Файл: Занятие 15 Адаптеры и списки в Android Studio Время 2 часа.docx

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 08.11.2023

Просмотров: 38

Скачиваний: 3

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Тема 2 Создание и тестирование модулей для мобильных приложений
Комплексное занятие 15 Адаптеры и списки в Android Studio
Время: 2 часа
Вопросы:

  1. Работа со списками

  2. Расширение списков и создание адаптера


1.Работа со списками
Android представляет широкую палитру элементов, которые представляют списки. Все они является наследниками класса android.widget.AdapterView. Это такие виджеты как ListView, GridView, Spinner. Они могут выступать контейнерами для других элементов управления, на рис. 1 показана иерархия этих элементов



Рис. 1 - Иерархия элементов
При работе со списками мы имеем дело с тремя компонентами.

Во-первых, это сам элементы списков (ListView, GridView), которые отображают данные.

Во-вторых, это источник данных - массив, объект ArrayList, база данных и т.д., в котором находятся сами отображаемые данные.

И в-третьих, это адаптеры - специальные компоненты, которые связывают источник данных с элементом списка.

Рассмотрим связь элемента ListView с источником данных с помощью одного из таких адаптеров - класса ArrayAdapter.


    1. Связь элемента ListView с источником данных с помощью одного из таких адаптеров - класса ArrayAdapter (App_54)



Посмотрим на примере. Сделайте разметку приложения так:
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

android:id="@+id/countriesList"
android:layout_width="match_parent"
android:layout_height="match_parent">




В файле разметки определен элемент ListView, который будет выводить список объектов.

Теперь перейдите к коду activity и свяжите ListView через ArrayAdapter с некоторыми данными:

package com.example.myapplication54;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends AppCompatActivity {

// набор данных, которые свяжем со списком
String[] countries = { "Бразилия", "Аргентина", "Колумбия", "Чили", "Уругвай"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// получаем элемент ListView
ListView countriesList = (ListView) findViewById(R.id.countriesList);

// создаем адаптер
ArrayAdapter adapter = new ArrayAdapter(this,
android.R.layout.simple_list_item_1, countries);

// устанавливаем для списка адаптер
countriesList.setAdapter(adapter);
}
}

Результат, см. рис. 1.



Рис. 1- Результат отображения в ListView
Здесь вначале получаем по id элемент ListView и затем создаем для него адаптер.

Для создания адаптера использовался следующий конструктор ArrayAdapter(this,android.R.layout.simple_list_item_1, countries), где

this : текущий объект activity

android.R.layout.simple_list_item_1 : файл разметки списка, который фреймворк представляет по умолчанию.

Он находится в папке Android SDK по пути platforms/[android-номер_версии]/data/res/layout. Если нас не удовлетворяет стандартная разметка списка, мы можем создать свою и потом в коде изменить id на id нужной нам разметки

countries : массив данных. Здесь необязательно указывать именно массив, это может быть список ArrayList.

В конце неоходимо установить для ListView адаптер с помощью метода setAdapter().


    1. Связь ресурса string-array и ListView (App_55)


В предыдущем под вопросе было рассмотрено, как выводить массив строк с помощью ArrayAdapter в ListView. При этом массив строк определялся программно в коде java. Однако подобный массив строк гораздо удобнее было бы хранить в файле xml в виде ресурса.

Ресурсы массивов строк представляют элемент типа string-array. Они могут находится в каталоге res/values в xml-файле с произвольным именем.

Определения массивов строк имеют следующий синтаксис:





    

        элемент

    


Массив строк задается с помощью элемента , атрибут name которого может иметь произвольное значение, по которому затем будут ссылаться на этот массив.

Все элементы массива представляют набор значений

Добавим в папку res/values новый файл. Для этого нажмем правой кнопкой мыши на данный каталог и появившемся меню выберем пункт New → Value resource file,см. рис. 2:



Рис. 2 – Добавление файла ресурса
В появившемся окне назовем файл как countries, см. рис.3:



Рис. 3 – Именование файла
После добавления файла в папку res/values изменим его содержимое следующим образом:




Бразилия
Аргентина
Колумбия
Чили
Уругвай


В файле разметки activity_main.xml остается определение элемента ListView:


android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">

android:id="@+id/countriesList"
android:layout_width="match_parent"
android:layout_height="match_parent">



Теперь свяжем ресурс и ListView в коде MainActivity:

package com.example.myapplication55;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// получаем элемент ListView
ListView countriesList = (ListView) findViewById(R.id.countriesList);

// получаем ресурс
String[] countries = getResources().getStringArray(R.array.countries);

// создаем адаптер
ArrayAdapter adapter = new ArrayAdapter(this,
android.R.layout.simple_list_item_1, countries);

// устанавливаем для списка адаптер
countriesList.setAdapter(adapter);
}
}
Для получения ресурса в коде java применяется выражение R.array.название_ресурса.

Но нам необязателно добавлять список строк в ListView программно. У этого элемента есть атрибут entries, который в качестве значения может принимать ресурс string-array:


android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">

android:entries="@array/countries"
android:id="@+id/countriesList"
android:layout_width="match_parent"
android:layout_height="match_parent">



В этом случае код MainActivity мы можем сократить до стандартного:

@Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

    }

}

Результат вы должны получить тот же.


    1. Выбор элемента в ListView (App_56)


В предыдущих под вопросах было рассмотрено, как можно загружать данные в ListView, связывать его с источником данных. Но кроме простого вывода списка элементов ListView позволяет выбирать элемент и обрабатывать его выбор. Рассмотрим, как это сделать. Определим следующую разметку в файле activity_main.xml:


android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

android:id="@+id/selection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="22sp" />
android:id="@+id/countriesList"
android:layout_width="match_parent"
android:layout_height="match_parent" />


Теперь свяжем список ListView с источником данных и закрепим за ним слушатель нажатия на элемент списка:

package com.example.myapplication56;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;

public class MainActivity extends AppCompatActivity {

String[] countries = { "Бразилия", "Аргентина", "Колумбия", "Чили", "Уругвай"};
private TextView selection;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// получаем элемент TextView
selection = (TextView) findViewById(R.id.selection);
// получаем элемент ListView
ListView countriesList = (ListView) findViewById(R.id.countriesList);

// создаем адаптер
ArrayAdapter adapter = new ArrayAdapter(this,
android.R.layout.simple_list_item_1, countries);
// устанавливаем для списка адаптер
countriesList.setAdapter(adapter);
// добвляем для списка слушатель
countriesList.setOnItemClickListener(new OnItemClickListener(){
@Override
public void onItemClick(AdapterView> parent, View v, int position, long id)
{
// по позиции получаем выбранный элемент
String selectedItem = countries[position];
// установка текста элемента TextView
selection.setText(selectedItem);
}
});
}
Метод setAdapter связывает элемент ListView с определенным адаптером. Далее для обработки выбора элемента списка устанавливается слушатель OnItemClickListener. Этот слушатель имеет один метод onItemClick, через параметры которого мы можем получить выделенный элемент и сопутствующие данные. Так, он принимает следующие параметры:

- parent : нажатый элемент AdapterView (в роли которого в данном случае выступает наш элемент ListView);

- view : нажатый виджет внутри AdapterView;

- position : индекс нажатого виждета внутри AdapterView;

- id : идентификатор строки нажатого элемента.

В итоге, получая индекс нажатого виджета, который соответствует индексу элемента в массиве строк, мы устанавливаем его текст в качестве текста элемента TextView (selection.setText(countries[position])).

Результат смотри на своем устройстве, см. рис.4.



Рис. 4 – Выбор элемента в ListView
    1. Множественный выбор в списке (App_57)


Иногда требуется выбрать не один элемент, как по умолчанию, а несколько. Для этого, во-первых, в разметке списка надо установить атрибут android:choiceMode="multipleChoice":


android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

android:id="@+id/selection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="22sp" />
android:choiceMode="multipleChoice"
android:id="@+id/countriesList"
android:layout_width="match_parent"
android:layout_height="match_parent" />



Теперь определим в коде MainActivity обработку выбора элементов списка:

package com.example.myapplication57;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import android.util.SparseBooleanArray;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;

public class MainActivity extends AppCompatActivity {

String[] countries = { "Смоленск", "Вологда", "Москва", "Сан-Хосе", "Чикаго"};
TextView selection;
ListView countriesList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// получаем элемент TextView
selection = (TextView) findViewById(R.id.selection);
// получаем элемент ListView
countriesList = (ListView) findViewById(R.id.countriesList);

// создаем адаптер
ArrayAdapter adapter = new ArrayAdapter(this,
android.R.layout.simple_list_item_multiple_choice, countries);
// устанавливаем для списка адаптер
countriesList.setAdapter(adapter);
// добвляем для списка слушатель
countriesList.setOnItemClickListener(new OnItemClickListener(){
@Override
public void onItemClick(AdapterView> parent, View v, int position, long id)
{
SparseBooleanArray sp=countriesList.getCheckedItemPositions();

String selectedItems="";
for(int i=0;i < countries.length;i++)
{
if(sp.get(i))
selectedItems+=countries[i]+",";
}
// установка текста элемента TextView
selection.setText("Выбрано: " + selectedItems);
}
});
}


Ресурс android.R.layout.simple_list_item_multiple_choice представляет стандартную разметку, предоставляемую фреймворком, для создания списка с множественным выбором.

А при выборе элементов мы получаем все выбранные позиции в объект SparseBooleanArray, затем пробегаемся по всему массиву, и если позиция элемента в массиве есть в SparseBooleanArray, то есть она отмечена, то добавляем отмеченный элемент в строку, результат см. рис.5.


Рис. 5 – Множественный выбор

1.5.Добавление и удаление в ListView (App_58)


После привязки ListView к источнику данных через адаптер мы можем работать с данными - добавлять, удалять, изменять только через адаптер. ListView служит только для отображения данных.

Для управления данными мы можем использовать методы адаптера или напрямую источника данных. Например, с помощью метода add() класса ArrayAdapter можно добавить новый элемент в конец массива-источника данных. Метод insert() позволяет добавить новое значение по определенному индексу, а метод remove() позволяет удалить объект из массива. С помощью метода sort() можно провести сортировку массива

Однако после применения вышеуказанных методов изменения коснутся только массива, выступающего источником данных. Чтобы синхронизировать изменения с элементом ListView, надо вызвать у адаптера метод notifyDataSetChanged().

Например, определим в файле activity_main.xml следующие элементы:

?xml version="1.0" encoding="utf-8"?>
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:id="@+id/phone"
android:layout_weight="4"
android:layout_width="0dp"
android:layout_height="wrap_content" />
android:layout_weight="1"
android:text="+"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:onClick="add"/>
android:layout_weight="1"
android:text="-"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:onClick="remove"/>

android:choiceMode="multipleChoice"
android:id="@+id/phonesList"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

Для вывода списка предназначен ListView с возможностью множественного выбора элементов. Для добавления и удаления определены две кнопки. Для ввода нового объекта в список предназначено поле EditText.

Теперь изменим класс MainActivity:

package com.example.myapplication58;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

ArrayList phones = new ArrayList();
ArrayAdapter adapter;

ArrayList selectedPhones = new ArrayList();
ListView phonesList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

phones.add("iPhone 7");
phones.add("Samsung Galaxy S7");
phones.add("Google Pixel");
phones.add("Huawei P10");
phones.add("HP Elite z3");

phonesList = (ListView) findViewById(R.id.phonesList);
adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_multiple_choice, phones);
phonesList.setAdapter(adapter);

// обработка установки и снятия отметки в списке
phonesList.setOnItemClickListener(new AdapterView.OnItemClickListener(){
@Override
public void onItemClick(AdapterView> parent, View v, int position, long id)
{
// получаем нажатый элемент
String phone = adapter.getItem(position);
if(phonesList.isItemChecked(position)==true){
selectedPhones.add(phone);
}
else{

selectedPhones.remove(phone);
}
}
});
}

public void add(View view){

EditText phoneEditText = (EditText) findViewById(R.id.phone);
String phone = phoneEditText.getText().toString();
if(!phone.isEmpty() && phones.contains(phone)==false){
adapter.add(phone);
phoneEditText.setText("");
adapter.notifyDataSetChanged();
}
}
public void remove(View view){
// получаем и удаляем выделенные элементы
for(int i=0; i< selectedPhones.size();i++){
adapter.remove(selectedPhones.get(i));
}
// снимаем все ранее установленные отметки
phonesList.clearChoices();
// очищаем массив выбраных объектов
selectedPhones.clear();

adapter.notifyDataSetChanged();
}
}
С добавлением все относительно просто: получаем введенную строку и добавляем в список с помощью метода adapter.add(). Чтобы обновить ListView после добавления вызывается метод adapter.notifyDataSetChanged().

А для удаления создается дополнительный список selectedPhones, который будет содержать выделенные элементы. Для получения выделенных элементов и добавления их в список используется слушатель AdapterView.OnItemClickListener, метод onItemClick() которого вызывается при установке или снятия отметки с элемента, то есть при любом нажатии на элемент.

По нажатию на кнопку удаления пробегаемся по списку выделенных элементов и вызываем для каждого из них метод adapter.remove(). Резульат на вашем устройстве, проведите добавление удаление элементов.


  1. Расширение списков и создание адаптера (App_59)



Обычные списки ListView, использующие стандартные адаптеры ArrayAdapter, хорошо работают с массивами строк. На практике мы будем сталкиваться с более сложными по структуре списками, где один элемент представляет не одну строку, а несколько строк, картинок и других компонентов.

Для создания сложного списка надо переопределить один из используемых адаптеров. Поскольку, как правило, используется ArrayAdapter, то именно его и переопределим.

Но вначале определим модель, данные которой будут отображаться в списке. Для этого добавим в тот же каталог, где находится класс MainActivity, новый класс. Для этого нажмем на данный каталог правой кнопкой мыши и в меню выберем New → Java Class, см. рис.6.



Рис. 6 – Добавление нового класса
В появившемся окне укажем для добавляемого класса имя В появившемся окне укажем для добавляемого класса имя State, см. рис.7..



Рис. 7 – Именование класса
После добавления изменим класс State следующим образом:

public class State {private String name; // название
private String capital; // столица
private int flagResource; // ресурс флага

public State(String name, String capital, int flag){

this.name=name;
this.capital=capital;
this.flagResource=flag;
}

public String getName() {
return this.name;
}

public void setName(String name) {
this.name = name;
}
public String getCapital() {
return this.capital;
}

public void setCapital(String capital) {
this.capital = capital;
}

public int getFlagResource() {
return this.flagResource;
}

public void setFlagResource(int flagResource) {
this.flagResource = flagResource;
}
}
Данный класс хранит два строковых поля - название государства и его столицу, а также числовое поле, которое будет указывать на ресурс изображения из папки drawable, которое будет отображать флаг государства.

Далее добавим в папку res/layout новый файл list_item.xml, который будет представлять разметку одного элемента в списке:




android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
android:id="@+id/flag"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
android:layout_width="70dp"
android:layout_height="50dp" />

android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">

android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Название" />

android:id="@+id/capital"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Столица" />




Каждый элемент будет иметь изображение в виде ImageView и два компонента TextView для отображения названия и столицы государства.

После этого добавим в каталог, где находятся классы MainActivity и State, новый класс, который назовем StateAdapter:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
public class StateAdapter extends ArrayAdapter {

private LayoutInflater inflater;
private int layout;
private List states;

public StateAdapter(Context context, int resource, List states) {
super(context, resource, states);
this.states = states;
this.layout = resource;
this.inflater = LayoutInflater.from(context);
}
public View getView(int position, View convertView, ViewGroup parent) {

View view=inflater.inflate(this.layout, parent, false);

ImageView flagView = (ImageView) view.findViewById(R.id.flag);
TextView nameView = (TextView) view.findViewById(R.id.name);
TextView capitalView = (TextView) view.findViewById(R.id.capital);

State state = states.get(position);

flagView.setImageResource(state.getFlagResource());
nameView.setText(state.getName());
capitalView.setText(state.getCapital());

return view;
}
}
Все взаимодействие со списком здесь будет идти через класс StateAdapter. В конструкторе StateAdapter нам надо передать в конструктор базового класса три параметра:

- контекст, в котором используется класс. В его роли как правило выступает класс Activity;

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

- набор объектов, которые будут выводиться в ListView.

В конструкторе StateAdapter мы получаем ресурс разметки и набор объектов и сохраняем их в отдельные переменные. Кроме того, для создания объекта View по полученному ресурсу разметки потребуется объект LayoutInflater, который также сохраняется в переменную.

В методе getView() устанавливается отображение элемента списка. Данный метод принимает три параметра:

- position: передает позицию элемента внутри адаптера, для которого создается представление;

- convertView: старое представление элемента, которое при наличии используется ListView в целях оптимизации;

- parent: родительский компонент для представления элемента.

В данном случае с помощью объекта LayoutInflater создаем объект View для каждого отдельного элемента в списке:

View view=inflater.inflate(this.layout, parent, false);

Из созданного объекта View получаем элементы ImageView и TextView по id:

ImageView flagView = (ImageView) view.findViewById(R.id.flag);

TextView nameView = (TextView) view.findViewById(R.id.name);

TextView capitalView = (TextView) view.findViewById(R.id.capital);

Это те элементы, которые определены в файле list_item.xml. Здесь же мы их получаем.

Далее используя параметр position, получаем объект State, для которого создается разметка:

State state = states.get(position);

Затем полученные элементы ImageView и TextView наполняем из полученного по позиции объекта State:

flagView.setImageResource(state.getFlagResource());

nameView.setText(state.getName());

capitalView.setText(state.getCapital());

И в конце созданный для отображения объекта State элемент View возвращается из метода:

return view;

Для использования изображений добавим в папку res/drawable несколько изображений, в моем случае это пять изображений флагов государств. В итоге проект будет выглядеть следующим образом, см. рис.8:



Рис. 8 – Состав проекта
В файле activity_main.xml определим ListView, в который будут загружаться данные:


android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

android:layout_height="match_parent"
android:layout_width="match_parent" />


/>
В файле MainActivity соединим StateAdapter с ListView:

package com.example.myapplication59;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

private List states = new ArrayList();

ListView countriesList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// начальная инициализация списка
setInitialData();
// получаем элемент ListView
countriesList = (ListView) findViewById(R.id.countriesList);
// создаем адаптер
StateAdapter StateAdapter = new StateAdapter(this, R.layout.list_item, states);
// устанавливаем адаптер
countriesList.setAdapter( StateAdapter);
// слушатель выбора в списке
AdapterView.OnItemClickListener itemListener = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View v, int position, long id) {

// получаем выбранный пункт
State selectedState = (State)parent.getItemAtPosition(position);
Toast.makeText(getApplicationContext(), "Был выбран пункт " + selectedState.getName(),
Toast.LENGTH_SHORT).show();
}
};
countriesList.setOnItemClickListener(itemListener);
}
private void setInitialData(){

states.add(new State ("Бразилия", "Бразилиа", R.drawable.ccc));
states.add(new State ("Аргентина", "Буэнос-Айрес", R.drawable.ger));
states.add(new State ("Колумбия", "Богота", R.drawable.ppp));
states.add(new State ("Уругвай", "Монтевидео", R.drawable.slovak));
states.add(new State("Чили", "Сантьяго", R.drawable.usa));
}
}


В качестве источника данных здесь выступает класс ArrayList, который получает данные в методе setInitialData. Каждому добавляемому объекту State в списке передается название государства, его столица и ресурс изображения из папки res/drawable, который представляет флаг государства.

При создании адаптера ему передается ранее созданный ресурс разметки list_item.xml и список states, результат на вашем устройстве.
Задание:

  1. Изучите материал и проведите все мероприятия самостоятельно.

  2. Пример второго вопроса имеет ошибки-преднамеренные устраните их!