Adapter android studio kotlin

Android Studio. Kotlin. Динамическая подгрузка данных в список RecyclerView

Долго я искал в сети способ сделать так, чтобы данные при построении списка RecyclerView не загружались целиком, а подгружались по мере его пролистывания пользователем. Несколько совершенно разных решений находил на StackOverflow. Пробовал применить — работало, но каждый раз, как-то криво и не надежно. После нескольких месяцев работы над проектом в режиме «Когда все дела сделаны и дети слезли с шеи», я наконец достиг, как мне кажется, идеального решения, чем и хочу поделиться в этой статье.

Задача

Мне нужно было отобразить для пользователя моего приложения список клиентов, консультаций и расходов из Базы Данных приложения в разных фрагментах. Один грамотный программист Баз Данных, по совместительству — мой шурин, объяснил мне что лучше отображать не все данные сразу, а только те, которые видны пользователю и реализовать возможность подгружать данные из БД по мере необходимости.

Скриншот главного экрана моего приложения

Решение

1. Настройка RecyclerView для отображения списка

В нескольких местах в сети прочел, что компонент ListView уже морально устарел. Подробно описывать работу RecyclerView не буду, дам лишь несколько кусков кода в качестве примера с короткими комментариями. Для работы со списком необходимы:

  • Единый макет для элементов спика (rc_timetable.xml).
  • Компонент RecyclerView в макете Активности (androidx.recyclerview.widget.RecyclerView).
  • Адаптер, отвечающий за отображение элементов списка (RecyclerView.Adapter)
  • Функция инициализации адаптера (fun initAdapter).
  • Функция заполнения списка (fun fillAdapter).
Читайте также:  Java lang illegalargumentexception malformed

1.1 Макет элементов списка

Макет элемента списка ничем не отличается от макетов экранов приложения. Я использую ConstraintLayout, в котором размещаю все, что мне необходимо показать пользователю в качестве отдельного элемента. Не забываю указать родительскому контейнеру layout_height = wrap_content.

Макет элемента спика

1.2 RecyclerView в макете Активности

Про добавление компонета RecyclerView мне сказать особо нечего. В макете Активности пишем его код или вставляем при помощи Визуального Дизайнера.

1.3 Адаптер

С адаптером дела обстоят несколько сложнее. Он должен быть описан при помощи двух классов, наследующихся от RecyclerView.Adapter и RecyclerView.ViewHolder соответственно. Первый, как я понял, отвечает за работу всего списка. А второй — создается для каждого элемента в отдельности и отрисовывает его.

Покажу на примере адаптера, отвечающего за отображение списка консультаций из календаря. В качестве параметра при создании объекта класса AdapterTimetable я передаю данные для построения списка в форме ArrayList

Сам код адаптера с некоторыми сокращениями выглядит следующим образом:

class AdapterTimetable( private var listItems: ArrayList ) : RecyclerView.Adapter() < private lateinit var el: RcTimetableBinding class MyHolder( itemView: View, private val el: RcTimetableBinding, ) : RecyclerView.ViewHolder(itemView) < fun drawItem(item: ListMeetings) < . // указываем время Встречи el.tvStartTime.text = item.startTime // указываем название Услуги el.tvService.text = "Консультация" // указываем тему Встречи el.tvTopic.text = "Тема Встречи" . >> override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder < val inflater = LayoutInflater.from(parent.context) el = RcTimetableBinding.inflate(inflater,parent,false) return MyHolder(el.root, context, el) >override fun onBindViewHolder(holder: MyHolder, position: Int) < // рисуем элемент списка holder.drawItem(listItems[position]) >override fun getItemCount(): Int < return listItems.size >fun updateAdapter(items: ArrayList) < // обновляем список listItems.clear() listItems.addAll(items) notifyDataSetChanged() >fun removeItem(pos: Int, calManager: CalManager) < // удаляем элемент из списка calManager.deleteMeeting(listItems[pos].uri) // удаляем встречу из календаря listItems.removeAt(pos) // удаляем элемент из списка с позиции pos notifyItemRangeChanged(0,listItems.size) // указываем адаптеру новый диапазон элементов notifyItemRemoved(pos) // указываем адаптеру, что один элемент удалился >>

Поясню вкратце вышеприведенный код.

Для обращения к компонентам макета из кода программы я использую некий viewBinding. Эксперты в сети советуют его вместо findViewById. Мне он понравился. Удобно обращаться к компонентам макета через одну переменную (в моей программе — это private val el: RcTimetableBinding). Подключается viewBinding в build.Gradle (Module) следующим образом:

В классе MyHolder единственная функция drawItem заполняет содержимым компоненты макета каждого элемента списка. В качестве параметра она получает данные типа ListMeetings.

В классе адаптера переопределяются три функции: onCreateViewHolder, onBindViewHolder и getItemCount. Первая «раздувает» макет элемента списка (inflate) при его создании. Вторая — наполняет элемент содержимым. А третья — возвращает количество элементов списка.

Также в адаптере должны присутствовать еще две функции: updateAdapter и removeItem. Первая обновляет содержимое списка, а вторая удаляет из него один элемент.

Надеюсь, что мои столь краткие комментарии достаточны, чтобы понять, как работает вышеприведенный код. Подробнее почитать о том, как работает RecyclerView вы можете, например, на сайте Александра Климова: http://developer.alexanderklimov.ru/android/views/recyclerview-kot.php

1.4 Функция инициализации адаптера

Адаптер используем в активности или фрагменте, который связан с макетом, содержащим RecyclerView. Указываем, что для отображения элементов списка будет использоваться LinearLayoutManager (элементы будут располагаться вертикально один под другим). Создаем adapter и присваеваем его нашему компоненту Recyclerview (rcView).

1.5 Функция заполнения списка

Здесь пока все просто — загружаем данные из Базы Данных (calManager.readMeetingsList) и обновляем список новыми данными (adapter.updateAdapter).

2. Динамическая подгрузка данных

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

2.1 Модернизация функции заполнения списка

fun fillAdapter(startDate: Long = 0, count: Int = Const.RC_ITEM_BUFFER, clear: Boolean = true) < // указываем в адаптере, что начинаем загрузку данных adapter.startLoading() val list = calManager.readMeetingsList(startDate, count) if (list.isNotEmpty()) adapter.updateAdapter(list, clear) // указываем, что загрузка данных закончена adapter.setLoaded() >

Надо сказать, что для отображения списка консультаций при запросе из Базы Данных я упорядочиваю их по возрастанию даты. И теперь функция fillAdapter принимает следующие параметры:

  • startDate — начальная дата, с которой берутся консультации.
  • _count — размер пакета данных, количество консультаций, которые будут отображаться.
  • clear — очищать или не очищать список.

Видно, что если вызвать функцию fillAdapter без параметров, то по умолчанию данные будут браться с самого начала, их количество будет равно некой константе RC_ITEM_BUFFER (в моем случае — 50) и список будет очищаться. Подобный вызов функции происходит в onResume:

Из кода видно, что изменились и вызовы функций calManager.readMeetingsList (она теперь возвращает только список консультаций с датой больше заданной и определенного количества) и adapter.updateAdapter (эта функция теперь содержит еще параметр clear — очищать ли список).

Вокруг блока кода, работающего с данными стоят строчки adapter.startLoading() и adapter.setLoaded() Это установка флага загрузки. Она необходима, чтобы при прокрутке списка не вызывалась слишком часто функция fillAdapter (подробнее смотрите далее).

2.2 Модернизация функции updateAdapter

fun updateAdapter(items: ArrayList, clear: Boolean = true)

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

2.3 Подгрузка данных и флаг загрузки

class AdapterTimetable( private var listItems: ArrayList ) : RecyclerView.Adapter() < private lateinit var el: RcTimetableBinding var loadMore : MyLoadMore? = null var isLoading = false . fun setLoadMore(loadMore: MyLoadMore?) < this.loadMore = loadMore >fun startLoading() < isLoading = true >fun setLoaded() < isLoading = false >. fun getLastItemDate(): Long < return if (listItems.size >0) listItems[listItems.size - 1].start else 0 > > interface MyLoadMore

Сначала про флаг загрузки. В классе адаптера вводим булеву переменную isLoading. Если она установлена в true, то значит происходит загрузка элементов и пока функция fillAdapter не доступна.

Подгрузка данных будет осуществляться при помощи функции onLoadMore, которая определяется через интерфейс MyLoadMore. Установливать ее содержимое будем из активности или фрагмента, связанного с RecyclerView при помощи функции setLoadMore Честно говоря, сам не понял, что сказал — для меня это уже слишком. Объясняю, как могу, ибо сам понимаю с трудом. Но смысл в том, чтобы иметь возможность вынести эту функцию за пределы адаптера в активность.

Ну и фунция, возвращающая дату последней консультации в списке, пригодится нам далее при подгрузке данных.

2.4 Модернизация функции initAdapter

private fun initAdapter() < el.rcView.layoutManager = LinearLayoutManager(requireContext()) adapter = AdapterTimetable(ArrayList()) el.rcView.adapter = adapter // при прокрутке запускаем onLoadMore val layoutManager = el.rcView.layoutManager as LinearLayoutManager el.rcView.addOnScrollListener(object: RecyclerView.OnScrollListener() < override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) < super.onScrolled(recyclerView, dx, dy) val totalItemCount = layoutManager.itemCount val lastVisibleItem = layoutManager.findLastVisibleItemPosition() if (!adapter.isLoading && totalItemCount > >) // переопределяем функцию onLoadMore adapter.setLoadMore(object : MyLoadMore < override fun onLoadMore() < fillAdapter(adapter.getLastItemDate(), Const.RC_ITEM_BUFFER, false, false) >>) >

К созданию адаптера добавляем две вещи: слушатель прокрутки (addOnScrollListener) и переопределение функции onLoadMore.

В слушателе прокрутки проверяем флаг загрузки и последний видимый элемент. Если положение списка близко к концу (RC_ITEM_BUFFER / 2), то подгружаем элементы при помощи модернизированной функции fillAdapter, указав в параметрах дату крайней консультации, размер пакета подгрузки и выключив очистку списка.

Ответ

Получилось вполне рабочее решение. Я его в таком виде в сети не встречал. Делюсь. Возможно, где-то в чем-то я перемудрил или не учел некоторые возможности, о которых просто пока понятия не имею. Буду рад вашим комментариям и предложениям. Есть вопрос про подгузку данных. Как вы думаете, насколько необходимо ее осуществлять при работе с БД на устройстве? Может, просто подтягивать все данные и грузить их целиком в список?

Собираюсь добавить возможность интеграции с Гугл Календарем. В связи с этим тоже возникает множество вопросов про списки RecyclerView. Там ведь повторяющиеся события, исключения ипрочие сложности.

Источник

How to write a custom adapter for my list view on Android using Kotlin?

This example demonstrates how to write a custom adapter for my list view on Android using Kotlin.

Step 1 − Create a new project in Android Studio, go to File ⇒ New Project and fill all required details to create a new project.

Step 2 − Add the following code to res/layout/activity_main.xml.

Step 3 − Add the following code to src/MainActivity.kt

import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.ListView import android.widget.TextView import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() < lateinit var listView: ListView var arrayList: ArrayList= ArrayList() var adapter: MyAdapter? = null override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) title = "KotlinApp" listView = findViewById(R.id.listView) arrayList.add(MyData(1, " Mashu", "987576443")) arrayList.add(MyData(2, " Azhar", "8787576768")) arrayList.add(MyData(3, " Niyaz", "65757657657")) adapter = MyAdapter(this, arrayList) listView.adapter = adapter >> //Class MyAdapter class MyAdapter(private val context: Context, private val arrayList: java.util.ArrayList) : BaseAdapter() < private lateinit var serialNum: TextView private lateinit var name: TextView private lateinit var contactNum: TextView override fun getCount(): Int < return arrayList.size >override fun getItem(position: Int): Any < return position >override fun getItemId(position: Int): Long < return position.toLong() >override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? < var convertView = convertView convertView = LayoutInflater.from(context).inflate(R.layout.row, parent, false) serialNum = convertView.findViewById(R.id.serialNumber) name = convertView.findViewById(R.id.studentName) contactNum = convertView.findViewById(R.id.mobileNum) serialNum.text = " " + arrayList[position].num name.text = arrayList[position].name contactNum.text = arrayList[position].mobileNumber return convertView >> //Class MyData class MyData(var num: Int, var name: String, var mobileNumber: String)

Step 4 − Create a layout resource file row.xml and add the following code

Step 5 − Add the following code to androidManifest.xml

Let’s try to run your application. I assume you have connected your actual Android Mobile device with your computer. To run the app from android studio, open one of your project’s activity files and click the Run icon from the toolbar. Select your mobile device as an option and then check your mobile device which will display your default screen.

Click here to download the project code.

Источник

Оцените статью