Создаем свой кастомный ListView

Недавно я озадачился созданием кастомного ListView, такого, чтоб вместо одной строки в каждом элементе списка было бы несколько строк: ну например заголовок и подзаголовок. Порылся по инету и пришел к выводу, что адекватного русскоязычного туториала на эту тему к сожалению нет. Ниче страшного. Теперь будет! :). Как минимум русскоязычный и надеюсь адекватный :).

Итак, прежде всего попробую описать логику создания такого ListView, а затем приведу пример с исходниками.

Начнем с того, что для отображения любой информации в ListView нам необходим адаптер — прослойка между вашими данными и их графическим отображением в ListView. Если мы хотим обычный список — то ту все здорово — используем готовый класс StringAdapter и радуемся. Передаем ему любой строковый массив, затем передаем наш адаптер в ListView и получаем готовый ListView заполненный элементами строкового массива. Однако, все становится гораздо сложнее, когда каждый элемент ListView содержит что-то большее чем одну строку. Для такого случая нам с Вами придется создать отдельный файл c разметкой (Layout) в котором будет хранится содержимое каждого элемента ListView. Поскольку мне нужен список с заголовком и подзаголовком я создал вот такой вот файлик:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/item_headerText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Large Text"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/item_subHeaderText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Medium Text"
        android:textAppearance="?android:attr/textAppearanceMedium" />

</LinearLayout>

В визуальном редакторе это выглядит вот так:

Внешний вид разметки

Внешний вид разметки

У нас в файле разметки имеется два текстовых поля: первое item_headerText и второе item_subHeaderText.

Следующим шагом создадим конструкцию с заголовком и подзаголовком. Я сделал это вот в таком небольшом классе:

package ru.davidmd.titorials.ListView;

public class Item {
	/**
	 * Заголовок
	 */
	String header;

	/**
	 * Подзаголовок
	 */
	String subHeader;

	/**
	 * Конструктор создает новый элемент в соответствии с передаваемыми
	 * параметрами:
	 * @param h - заголовок элемента
	 * @param s - подзаголовок
	 */
	Item(String h, String s){
		this.header=h;
		this.subHeader=s;
	}

	//Всякие гетеры и сеттеры
	public String getHeader() {
		return header;
	}
	public void setHeader(String header) {
		this.header = header;
	}
	public String getSubHeader() {
		return subHeader;
	}
	public void setSubHeader(String subHeader) {
		this.subHeader = subHeader;
	}

}

Теперь приступим к реализации класса-адаптера. Фактически именно класс-адаптер создает каждый элемент в ListView а значит нашей задачей будет именно создание каждого элемента. Помимо этого на класс адаптер возлагаются еще некоторые другие функции. Лично я свой адаптер унаследовал от абстрактного класса BaseAdapter. Нам необходимо реализовать несколько его методов. Что я собственно и сделал:

 

package ru.davidmd.titorials.ListView;

import java.util.ArrayList;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class MyAdapter extends BaseAdapter {

	ArrayList<Item> data = new ArrayList<Item>();
	Context context;

	public MyAdapter(Context context, ArrayList<Item> arr) {
		if (arr != null) {
			data = arr;
		}
		this.context = context;
	}

	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return data.size();
	}

	@Override
	public Object getItem(int num) {
		// TODO Auto-generated method stub
		return data.get(num);
	}

	@Override
	public long getItemId(int arg0) {
		return arg0;
	}

	@Override
	public View getView(int i, View someView, ViewGroup arg2) {
		//Получение объекта inflater из контекста
		LayoutInflater inflater = LayoutInflater.from(context);
		//Если someView (View из ListView) вдруг оказался равен
		//null тогда мы загружаем его с помошью inflater
		if (someView == null) {
			someView = inflater.inflate(R.layout.list_view_item, arg2, false);
		}
		//Обявляем наши текствьюшки и связываем их с разметкой
		TextView header = (TextView) someView.findViewById(R.id.item_headerText);
		TextView subHeader = (TextView) someView.findViewById(R.id.item_subHeaderText);

		//Устанавливаем в каждую текствьюшку соответствующий текст
		// сначала заголовок
		header.setText(data.get(i).header);
		// потом подзаголовок
		subHeader.setText(data.get(i).subHeader);
		return someView;
	}

}

Теперь давайте смотреть что тут к чему: конструктор MyAdapter() получает два параметра: первый — контекст, второй массив значений из которых мы будем строить наш ListView. Методы getCount() и getItem() думаю в пояснении не нуждаются. Самое интересное происходит в методе getView(). Этот метод вызывается из ListView тогда, когда листвью пытается заполнить саму себя с помощью адаптера. В качестве параметров сюда приходят номер элемента i, который в данный момент пытается добавить ListView, пустой объект someView — это как раз тот самый View который мы с вами будем заполнять и экземпляр ViewGroup — который есть ни что иное как сам заполняемый ListView.

В этом методе сначала мы получаем экземпляр класса LayoutInflater — это класс который используется для создания объектов View из их описания в xml. Затем в переданный нам someView мы закидываем объект View полученный из нашего файла разметки. И настраиваем его параметры — такие как заголовок и подзаголовок! Ничего сложного, ура!

Ну и последний элемент — это собственно сам класс Activity:

 

package ru.davidmd.titorials.ListView;

import java.util.ArrayList;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;

public class ListViewTutorialActivity extends Activity implements OnItemcClickListener {
    /** Called when the activity is first created. */

	ListView lv;
	ArrayList<Item> data = new ArrayList<Item>();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        data.add(new Item("Заголовок1","Подзаголовок1"));
        data.add(new Item("Заголовок2","Подзаголовок2"));
        data.add(new Item("Заголовок3","Подзаголовок3"));
        data.add(new Item("Заголовок4","Подзаголовок4"));
        data.add(new Item("Заголовок5","Подзаголовок5"));
        lv = (ListView) this.findViewById(R.id.listView1);
        lv.setAdapter(new MyAdapter(this,data));
        lv.setOnItemClickListenr(this);
    }

    @Override
    public void onItemClick (AdapterView<?> parent, View view, int position, long id){
        Toast.MakeText(this, ((Item)((MyAdapter)parent).getItem(position)).header, Toast.LENGTH_LONG).show();
    }
}

Здесь тоже никаких проблем возникнуть не должно. Формируем список объектов и закидываем его в создаваемый анонимный адаптер. Ура, все работает. Вот тут ListViewTutorial можно скачать проект для Eclipse.

Метод onItemClick добавлен по просьбе Dmitrii. Заметьте, что наша активити реализует интерфейс OnItemClickListenr.  Соответственно метод onItemClick вызывается когда произойдет клик на каком-то из элементов списка. При клике мы создаем всплывающе уведомление Toast, в котом показываем заголовок того элемента списка по которому тапнул пользователь.

Если возникли вопросы или пожелания — пишите! Постараюсь ответить!

Вам понравилось? Было полезно? Поделитесь!

Опубликовать в Facebook
Опубликовать в Google Buzz
Опубликовать в Google Plus
Опубликовать в LiveJournal
Опубликовать в Мой Мир
Опубликовать в Одноклассники
Опубликовать в Яндекс
Запись опубликована в рубрике Android, Новичку, Программирование с метками , , , , , , , . Добавьте в закладки постоянную ссылку.

26 Responses to Создаем свой кастомный ListView

  1. anonim говорит:

    Спасибо, получилось. А как теперь добавить элемент в этот список при нажатии на кнопку?

    • davidmd говорит:

      Вам надо дописать в класс MyAdapter метод, позволяющий на лету добавлять в массив ваших элементов новые. Ну и потом обновлять каким-то образом.

      • Михаил говорит:

        У меня вопрос следующий. Я создал список. Заполнил его. Сделал, чтобы при нажатии на элемент списка открывалось диалоговое окно. В диалоговом окне есть поле EditText и кнопка «Ок». Вот как мне сделать, чтобы по нажатию кнопки «Ок», текст, который я вписал в EditText, заменял бы текст в определенном элементе списка (по которому я нажал)?

        Если поможете — буду очень благодарен!

    • Aspid говорит:

      Реализуй интерфейс onClickListener:
      public class ListViewTutorialActivity extends Activity implements onClickListener

      В методе onCreate привязываем обработчик нажатия к кнопке:
      btnAdd.setOnClickListener(this);

      В унаследованном методе onClick описываем добавление View в ListView:
      public void onClick(View v) {
      switch (v.getId()) {
      case R.id.btnAdd:
      data.add(new Item(«Заголовок6″,»Подзаголовок6»));

      lv = (ListView) this.findViewById(R.id.listView1);
      lv.setAdapter(new MyAdapter(this,data));

      break;
      }
      }

      Что-то похожее на это должно помочь.

  2. Юрий говорит:

    А если мне надо вывести не линейный список, а таблицу с колонками определенной ширины надо пользоваться ListView или чем то еще? Как в этом случае адекватно построить Layout?

  3. zgollum говорит:

    Стоило, наверное, упомянуть о паттерне ViewHolder, позволяющем не вызывать каждый раз findViewById, тогда бы адаптер был совсем «кошерным».

  4. Sabre говорит:

    Подскажите, пожалуйста, как сделать так, чтобы при нажатии на элемент списка открывался файл из папки assets?
    Заранее спасибо!

  5. Alexey говорит:

    Подскажите как сделать мультивыбор в ListView не используя CheckBox? т.е. жму на item и он становится selected.

    • davidmd говорит:

      Если я все правильно понимаю, то стандартные компоненты такого не умеют 🙂 Пидется переписывать 🙂

  6. Vladsialv говорит:

    Здраствуйте! Как с вами можно связаться кроме e-mail? ICQ\Skype?

  7. mamba говорит:

    Здравствуйте подскажите новичку открыл ваш проект

    http://s42.radikal.ru/i097/1305/94/f90ede5b0bba.jpg

  8. Даниил говорит:

    Хорошая статья! Спасибо! Пожалуйста опубликуйте подобную только как вставить картинку перед названием, и как работать с кнопками? Спасибо!

    • davidmd говорит:

      методика работы точно такая же! Просто добавляете ImageView на лэйаут. ДЛя кнопок то же все очень похоже!

  9. Тимур говорит:

    А у меня не работает.

    Данные передаю, а в listview список не отображается почему то((

  10. Nik говорит:

    Добрый день. Я хочу данные брать из строкового ресурса strings, для этого создал там два массива string-array, один для заголовка, другой для подзаголовка. Как теперь это прописать в активити?

  11. Nik говорит:

    Здравствуйте, у меня ещё один вопрос:
    Изначально мой код активности выглядел таким образом:
    public class SpisokActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_spisok);

    ListView lv;
    ArrayList data = new ArrayList();

    data.add(new Item(«arise», «arose», «arisen», «Возникать»));
    data.add(new Item(«be», «was / were», «been», «Быть»));
    data.add(new Item(«bear», «bore», «born / borne», «Родить»));
    data.add(new Item(«beat», «beat», «beaten / beat», «Бить»));
    data.add(new Item(«become», «became», «become», «Становиться»));
    lv = (ListView) this.findViewById(R.id.list);
    lv.setAdapter(new MyAdapter(this, data));
    }
    }
    Код рабочий, но встал вопрос с отработкой нажатия, тогда я написал вот так:
    public class SpisokActivity extends android.app.ListActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_spisok);

    ListView lv;
    ArrayList data = new ArrayList();

    data.add(new Item(«arise», «arose», «arisen», «Возникать»));
    data.add(new Item(«be», «was / were», «been», «Быть»));
    data.add(new Item(«bear», «bore», «born / borne», «Родить»));
    data.add(new Item(«beat», «beat», «beaten / beat», «Бить»));
    data.add(new Item(«become», «became», «become», «Становиться»));
    lv = (ListView) this.findViewById(R.id.list);
    lv.setAdapter(new MyAdapter(this, data));
    }
    // Обработка нажатия
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
    super.onListItemClick(l, v, position, id);
    }
    }
    То есть, что бы добавить метод onListItemClick я изменил активность AppCompatActivity на android.app.ListActivity. Ошибки в коде AndroidStudio не выдает, на телефон и эмулятор программа устанавливается, но при попытке обратиться к данной активности программа вылетает.

  12. Артур говорит:

    Здравствуйте! Огромное спасибо за статью! Не могли бы вы помочь с реализацией поиска по ListView с двумя TextView? Мне нужно чтобы при вводе чисел в EditText, поиск производился по ListView, TextView1, а при вводе текста по ListView, TextView2… И чтобы при вводе текста, фильтровалось только по началу текста в ListView а не в любом месте..
    Например: Есть ListView с двумя TextView, txtItem1 и txtItem2 выглядит это вот так:
    (txtItem1) (txtItem2)
    №песни Название песни
    1 Вся молодёжь Казахстана
    2 Мы семья, мы одна семья
    3 Если пришла беда…
    4 Ясли и Крест — чудная весть
    При вводе в EditText числа 2 чтобы в ListView оставалась песня только под номером 2, а при вводе буквы Е чтобы оставались песни которые только начинаются на Е, а не в которых есть буква е.. Надеюсь я понятно объяснил. Буду очень благодарен за помощь

  13. Артур говорит:

    Чтобы было понятней, мне нужно реализовать точно такой же поиск как в данном приложении ->
    https://play.google.com/store/apps/details?id=com.blagoyouth.hymns3300&hl=ru

    Сам я уже долго пытаюсь, но ничего не получается… А связаться с автором приложения не могу…

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *