Предисловие
На днях столкнулся с проблемой, нужно было запарсить большой объём данных из JSON и вывести все объекты в ListView.
Серди данных были ссылки на изображения, числовые данные и текст.
Изначально начал использовать SimpleAdapter, но вставлялся только текст, вместо картинки было пустое место.
Долго гуглил данную проблему, везде предлагалось то переводить в Bitmap, то ViewBinder и т.д. И было принято решение написать собственный Adapter наследуемый от ArrayAdapter.
Входные данные
При парсинге я получал данные:
- 1. id (int)
- 2. Name (String)
- 3. Genres (String[])
- 4. Tracks (int)
- 5. Albums (int)
- 6. Link (String)
- 7. Decription (String)
- 8. Cover (JSONObject) содержащий:
small (String) и big (String)
В small и big содержатся ссылки на изображения каждого объекта.
В ListView нужно было вывести Имя (Name), Жанры (Genres), Количество песен и альбомов (Tracks+Albums) и картинку (Small). Все остальные данные нужны для подробного описания на втором экране.
Собираем данные
Так как мы не знаем количество получаемых объектов, то будем использовать отдельный ArrayList на каждое поле, чтобы потом передать это всё в наш CustomListAdapter.
Проходим в цикле по каждому JSONObject и получаем нужные нам данные:
for (int i = 0; i < rootJSON.length(); i++) {
count++;
artist = rootJSON.getJSONObject(i);
name = artist.getString("name");
nameAr.add(count,name);
genresA = artist.getJSONArray("genres");
genres = "";
for (int j = 0; j < genresA.length(); j++) {
if (j==0) genres += genresA.getString(j);
else genres += ", " + genresA.getString(j) ;
}
genresAr.add(count,genres);
tracks = artist.getInt("tracks");
// проверка на написание слова "Песня" в зависимости от значения tracks, записываем нужное слово в строку TR
albums = artist.getInt("albums");
// проверка на написание слова "Альбом" в зависимости от значения albums, записываем нужное слово в строку AL
tracksAr.add(count,String.valueOf(albums)+al+", "+ String.valueOf(tracks)+tr);
cover = artist.getJSONObject("cover");
small = cover.getString("small");
smallAr.add(count,small);
}
Теперь приступим к самому главному, написанию нашего Адаптера. В него мы будем передавать context и все наши ArrayList.
Создаём новый класс CustomListAdapter:
public class CustomListAdapter extends ArrayAdapter<String> {
private final Activity context;
private final ArrayList<String> nameAr,genresAr,tracksAr, smallAr;
public CustomListAdapter(Activity context, ArrayList<String> nameAr, ArrayList<String> genresAr, ArrayList<String> tracksAr,ArrayList<String> smallAr) {
}
public View getView(int position,View view,ViewGroup parent) {
}
}
В public CustomListAdapter мы должны определить поля класса:
public CustomListAdapter(Activity context, ArrayList<String> nameAr, ArrayList<String> genresAr, ArrayList<String> tracksAr, ArrayList<String> smallAr) {
super(context, R.layout.item, nameAr);
this.context=context;
this.nameAr=nameAr;
this.genresAr=genresAr;
this.tracksAr=tracksAr;
this.smallAr=smallAr;
}
В public View getView мы должны написать что будет делать наш Адаптер. Для отображения элемента ListView создаём новый layout item.xml, в моём случае у меня в нём находятся ImageView и 3 TextView.
public View getView(int position,View view,ViewGroup parent) {
LayoutInflater inflater=context.getLayoutInflater();
View rowView=inflater.inflate(R.layout.item, null,true);
//устанавливаем тексты и картинку в элемент списка
ImageView imageView = (ImageView) rowView.findViewById(R.id.imgSmall);
TextView name = (TextView) rowView.findViewById(R.id.name);
TextView genres = (TextView) rowView.findViewById(R.id.genres);
TextView tracks = (TextView) rowView.findViewById(R.id.tracks);
//загружаем картинку по ссылке, если картинка уже была загружена, берём её из кэша
Picasso.with(context)
.load(smallAr.get(position))
.into(imageView);
name.setText(nameAr.get(position));
genres.setText(genresAr.get(position));
tracks.setText(tracksAr.get(position));
return rowView;
}
И теперь нам остаётся в MainActivity вызвать наш Адаптер после того, как мы собрали все данные в массивы.
artists = (ListView) findViewById(R.id.artistList);
CustomListAdapter adapter = new CustomListAdapter(main, nameAr, genresAr, tracksAr, smallAr);
artists.setAdapter(adapter);
Адаптер готов, можем запускать.
Весь проект доступен на GitHub: https://github.com/sergon146/Song
Для вставки изображений по URL использовал стороннюю библиотеку Picasso, она проста в использовании и при загрузке автоматически кэширует.
Комментарии (16)
MediumMG
27.04.2016 16:24+3имхо, с введением RecyclerView в support библиотеках ListView умер и использовать его — анахронизм. А в RecyclerView по умолчанию используется паттерн ViewHolder'a.
И даже при использовании Data Binding библиотеки проблем с подгрузкой картинок не возникает — подобная ситуация описана в самом гайде (здесь)sergeygonin
27.04.2016 16:26-2Я пока что начинающий Android-разработчик, прошу не относиться так строго) А этот пример один из самых простых и понятных, по крайней мере для меня.
Думаю и другим новичкам она будет полезна.MediumMG
27.04.2016 17:02Почему-то ссылка не вставилась — http://developer.android.com/tools/data-binding/guide.html#advanced_binding
Так это прекрасно, что начинающий — можете сразу начинать использовать последние компоненты и техники. Тот же RecyclerView, тот же MVVM совместно с Data Binding'ом…
И потом не будете случаем путаться мужду старыми\новыми реализациями)
zagayevskiy
27.04.2016 18:59+5А зачем эта статья на хабре?
sergeygonin
27.04.2016 19:01-2Я долго искал как сделать что-то подобное, почти ничего не нашёл, и решил написать
zagayevskiy
27.04.2016 19:16+1https://habrahabr.ru/search/?q=listview+adapter
Плохо искал, садись, два.
almkhaj
28.04.2016 10:33Видно, что товарищ только начал изучать программирование под Android. Соглашусь, что таким статьям здесь не место.
Mihail57
27.04.2016 16:39+1Тема интересная для начинающих андроид-разработчиков, но как-то не радует исполнение. Первый вопрос: не легче ли было сделать в чуть более ООП стиле? Сделать класс для одной песни, в котором были бы поля жанр, название, исполнитель(-и) и т.п.
И как уже сказал господин SannX, необходимо проверять код, который публикуете, на производительность и не брезгать встроенными инструментами оптимизации кода, которые за нас уже придумали в Google. Хотя бы переиспользовать View желательно было бы, зачем нам плодить сотню объектов в памяти?Mihail57
27.04.2016 16:41Хотя я видел пример еще большего расточительства памяти — объекты создавались до работы с адаптером и уже по id брались из подготовленного списка.
Beanut
27.04.2016 19:07+2Создал свой первый адаптер — пиши статью на хабр. Тут ведь даже viewholder-паттерн есть. O wai…
SannX
Производительность вашего getView оставляет желать лучшего: каждый View, создаваемый по XML, — это отдельный джава-объект. Расположение (inflating) лайоутов и создание объектов — дорогостоящие операции. Кроме этого, вызов findViewById также занимает некоторое время на выполнение. Поэтому в кастомных getView нужно использовать паттерн «View holder». Подробности здесь
sergeygonin
Спасибо за отзыв, постараюсь переделать!