Предисловие


На днях столкнулся с проблемой, нужно было запарсить большой объём данных из 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 и получаем нужные нам данные:


Код из MainActivity
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:


CustomListAdapter.class

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 мы должны определить поля класса:


Код 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.


Код getView
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);

Адаптер готов, можем запускать.


Скрин MainActivity

Весь проект доступен на GitHub: https://github.com/sergon146/Song


Для вставки изображений по URL использовал стороннюю библиотеку Picasso, она проста в использовании и при загрузке автоматически кэширует.

Комментарии (16)


  1. SannX
    27.04.2016 13:30
    +4

    Производительность вашего getView оставляет желать лучшего: каждый View, создаваемый по XML, — это отдельный джава-объект. Расположение (inflating) лайоутов и создание объектов — дорогостоящие операции. Кроме этого, вызов findViewById также занимает некоторое время на выполнение. Поэтому в кастомных getView нужно использовать паттерн «View holder». Подробности здесь


    1. sergeygonin
      27.04.2016 13:34
      -1

      Спасибо за отзыв, постараюсь переделать!


  1. MediumMG
    27.04.2016 16:24
    +3

    имхо, с введением RecyclerView в support библиотеках ListView умер и использовать его — анахронизм. А в RecyclerView по умолчанию используется паттерн ViewHolder'a.

    И даже при использовании Data Binding библиотеки проблем с подгрузкой картинок не возникает — подобная ситуация описана в самом гайде (здесь)


    1. sergeygonin
      27.04.2016 16:26
      -2

      Я пока что начинающий Android-разработчик, прошу не относиться так строго) А этот пример один из самых простых и понятных, по крайней мере для меня.
      Думаю и другим новичкам она будет полезна.


      1. MediumMG
        27.04.2016 17:02

        Почему-то ссылка не вставилась — http://developer.android.com/tools/data-binding/guide.html#advanced_binding

        Так это прекрасно, что начинающий — можете сразу начинать использовать последние компоненты и техники. Тот же RecyclerView, тот же MVVM совместно с Data Binding'ом…
        И потом не будете случаем путаться мужду старыми\новыми реализациями)


        1. sergeygonin
          27.04.2016 17:04
          -1

          Спасибо, в ближайшее время изучу подробнее данный материал!)


      1. zagayevskiy
        27.04.2016 18:59
        +5

        А зачем эта статья на хабре?


        1. sergeygonin
          27.04.2016 19:01
          -2

          Я долго искал как сделать что-то подобное, почти ничего не нашёл, и решил написать


          1. Beanut
            27.04.2016 19:14
            +3

            Google: «android listview». 2я ссылка сверху. Хабр 2011 год.


          1. zagayevskiy
            27.04.2016 19:16
            +1

            https://habrahabr.ru/search/?q=listview+adapter
            Плохо искал, садись, два.


        1. almkhaj
          28.04.2016 10:33

          Видно, что товарищ только начал изучать программирование под Android. Соглашусь, что таким статьям здесь не место.


  1. Mihail57
    27.04.2016 16:39
    +1

    Тема интересная для начинающих андроид-разработчиков, но как-то не радует исполнение. Первый вопрос: не легче ли было сделать в чуть более ООП стиле? Сделать класс для одной песни, в котором были бы поля жанр, название, исполнитель(-и) и т.п.

    И как уже сказал господин SannX, необходимо проверять код, который публикуете, на производительность и не брезгать встроенными инструментами оптимизации кода, которые за нас уже придумали в Google. Хотя бы переиспользовать View желательно было бы, зачем нам плодить сотню объектов в памяти?


    1. Mihail57
      27.04.2016 16:41

      Хотя я видел пример еще большего расточительства памяти — объекты создавались до работы с адаптером и уже по id брались из подготовленного списка.


  1. Beanut
    27.04.2016 19:07
    +2

    Создал свой первый адаптер — пиши статью на хабр. Тут ведь даже viewholder-паттерн есть. O wai…


  1. Beanut
    27.04.2016 19:11

    дубль


  1. Tiberal
    28.04.2016 10:33

    Теперь я понял зачем в RecyclerView сделали принудительный ViewHolder.