Настройка listview items с собственным ArrayAdapter


Для реальных коммерческих мобильных приложений, стиль по-умолчанию и вид ListView в Android не привлекателен. Он может лишь отображать простую строку в каждой строке ListView, используя внутренний контролл (элемент) TextView. Для большинства приложений, вы захотите создать интерфейс, который более приятен для пользователя. Хорошо, что ListView очень мощный контрол и с помощью своих разметок элемента он может быть легко изменен что бы соответствовать вашим потребностям. В этом руководстве я покажу вам, как вы можете создать свои элементы ListView с иконками, своей разметкой заголовка и как вы можете  использовать настроенный ArrayAdapter что бы создавать что угодно на основе ArrayAdapter в будущем. Я также расскажу некоторые советы, которые вы можете использовать для оптимизации использования памяти вашим ListView.

Android_Custom_ListView_ArrayAdapter

Создайте новый проект Android в Eclipse с MainActivity, которая будет Activity-по умолчанию, а так же main.xml как разметка для этой Activity. Объявите элемент ListView в вашем файле разметки main.xml, как показано в следующем коде:

Main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#FFFFFF"> 
 
     <ListView
        android:id="@+id/listView1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
 
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#FFFFFF"> 

     <ListView
        android:id="@+id/listView1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</LinearLayout>

Код выше использует простой LinearLayout с вертикальной ориентацией и ListView объявляемый что бы покрыть всю ширину и высоту родительского контейнера используется fill_parent как значение обоих android:layout_heightиandroid:layout:width свойствах. ListView так же имеет уникальный id listView1, который будет использоваться в MainActivity что бы ссылаться на ListView.

Чтобы создать кастомизированный заголовок для ListView, создайте новый файл разметки xml -listview_header_row.xml в папке layout вашего проекта и объявите TextView его свойства показаны в следующем коде. Это создаст синий заголовок с белым текстом.

listview_header_row.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"> 
 
     <TextView android:id="@+id/txtHeader"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center_vertical"
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:textStyle="bold"
        android:textSize="22dp"
        android:textColor="#FFFFFF"
        android:padding="10dp"
        android:text="Weather Photos"
        android:background="#336699" />
 
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"> 

     <TextView android:id="@+id/txtHeader"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center_vertical"
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:textStyle="bold"
        android:textSize="22dp"
        android:textColor="#FFFFFF"
        android:padding="10dp"
        android:text="Weather Photos"
        android:background="#336699" />

</LinearLayout>

Что бы создать строку ListView, создайте другой файл разметки с именем listview_item_row.xml в папке layout вашего проекта. Android будет отрисовывать содержимое этого файла в каждом элементе ListView и вы можете определить любой контролл, который захотите. Для этого руководства я использую ImageView для иконки и TextView для отображения заголовков. Следующий код для listview_item_row.xml

listview_item_row.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="10dp">
 
     <ImageView android:id="@+id/imgIcon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:gravity="center_vertical"
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="15dp"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp" />
 
     <TextView android:id="@+id/txtTitle"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center_vertical"
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:textStyle="bold"
        android:textSize="22dp"
        android:textColor="#000000"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp" />
 
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="10dp">

     <ImageView android:id="@+id/imgIcon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:gravity="center_vertical"
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="15dp"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp" />

     <TextView android:id="@+id/txtTitle"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center_vertical"
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:textStyle="bold"
        android:textSize="22dp"
        android:textColor="#000000"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp" />

</LinearLayout>

Для этого руководства я скачал некоторые иконки 32 x 32 в формате PNG. Вы можете использовать ваши собственные иконки по своему усмотрению. Когда иконки готовы перетащите иконки из вашей директории (папки) в папку  drawable-mdpi внутри вашего проекта. Далее создайте новый класс JAVA в вашем проекте с именем Weather.java. Этот класс будет использоваться кастомизированным ArrayAdapter и будет позже связывать объекты с ListView. Следующий код для  Weather.java. В нем есть два простых свойства: иконка и заголовок, а так же простой конструктор класса, что бы инициализировать свойства.

Weather.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Weather {
    public int icon;
    public String title;
    public Weather(){
        super();
    }
 
    public Weather(int icon, String title) {
        super();
        this.icon = icon;
        this.title = title;
    }
}
public class Weather {
    public int icon;
    public String title;
    public Weather(){
        super();
    }

    public Weather(int icon, String title) {
        super();
        this.icon = icon;
        this.title = title;
    }
}

Заметьте, что выше  файл listview_item_row.xml имеет 2 view, которые передают свойства класса Weather. Значения свойств класса Weather будут отображаться на их view а так же соениять эти 2 места вместе вы должны создать кастомный ArrayAdapter, который будет наследоваться от класса  Android ArrayAdapter и будет переопределять метод getView. Добавьте java-класс в ваш проект под именем nameWeatherAdapter и реализуйте код, который показан ниже.

 WeatherAdapter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class WeatherAdapter extends ArrayAdapter<Weather>{
 
    Context context; 
    int layoutResourceId;    
    Weather data[] = null;
 
    public WeatherAdapter(Context context, int layoutResourceId, Weather[] data) {
        super(context, layoutResourceId, data);
        this.layoutResourceId = layoutResourceId;
        this.context = context;
        this.data = data;
    }
 
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        WeatherHolder holder = null;
 
        if(row == null)
        {
            LayoutInflater inflater = ((Activity)context).getLayoutInflater();
            row = inflater.inflate(layoutResourceId, parent, false);
 
            holder = new WeatherHolder();
            holder.imgIcon = (ImageView)row.findViewById(R.id.imgIcon);
            holder.txtTitle = (TextView)row.findViewById(R.id.txtTitle);
 
            row.setTag(holder);
        }
        else
        {
            holder = (WeatherHolder)row.getTag();
        }
 
        Weather weather = data[position];
        holder.txtTitle.setText(weather.title);
        holder.imgIcon.setImageResource(weather.icon);
 
        return row;
    }
 
    static class WeatherHolder
    {
        ImageView imgIcon;
        TextView txtTitle;
    }
}
public class WeatherAdapter extends ArrayAdapter<Weather>{

    Context context; 
    int layoutResourceId;    
    Weather data[] = null;

    public WeatherAdapter(Context context, int layoutResourceId, Weather[] data) {
        super(context, layoutResourceId, data);
        this.layoutResourceId = layoutResourceId;
        this.context = context;
        this.data = data;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        WeatherHolder holder = null;

        if(row == null)
        {
            LayoutInflater inflater = ((Activity)context).getLayoutInflater();
            row = inflater.inflate(layoutResourceId, parent, false);

            holder = new WeatherHolder();
            holder.imgIcon = (ImageView)row.findViewById(R.id.imgIcon);
            holder.txtTitle = (TextView)row.findViewById(R.id.txtTitle);

            row.setTag(holder);
        }
        else
        {
            holder = (WeatherHolder)row.getTag();
        }

        Weather weather = data[position];
        holder.txtTitle.setText(weather.title);
        holder.imgIcon.setImageResource(weather.icon);

        return row;
    }

    static class WeatherHolder
    {
        ImageView imgIcon;
        TextView txtTitle;
    }
}

В коде выше первая важная вещь — это конструктор класса, у которого есть три параметра. Первый параметр это Context и мы можем ссылаться на activity в которой мы будем использовать класс WeatherAdapter. Второй параметр это идентификатор ресурса файла разметки, который мы хотим использовать что бы отображать каждый элемент в ListView. Мы будем получать идентификатор ресурса файла разметки listview_item_row.xml в этом параметре. Третий параметр — это массив экземпляров класса Weather, который будет использоваться адаптером, для отображения данных.

Метод getView из родительского класса переопределяется. Этот метод будет вызываться для каждого элемента в ListView что бы создать представления с их свойствами, как мы хотим. Метод getView также используется как временное хранилище класса определенного внутри класса  WeatherAdapter. Этот класс будет использовать что бы кешировать ImageView и TextView, они могут быть использованы снова для каждой строки в ListView и он будет обеспечивать нас хорошим увеличением производительности, так как мы можем переопределить некоторые два представления с разными свойствами и нам не нужно искать элементы ImageView и TextView для каждого элемента списка ListView. Код выше также используется Android built в Layout Inflator что бы обрабатывать файл разметки.

Последняя часть кода это MainActivity который будет использовать объекты, объявленные выше. Следующий код из файла MainActivity.java

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class MainActivity extends Activity {
 
    private ListView listView1;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        Weather weather_data[] = new Weather[]
        {
            new Weather(R.drawable.weather_cloudy, "Cloudy"),
            new Weather(R.drawable.weather_showers, "Showers"),
            new Weather(R.drawable.weather_snow, "Snow"),
            new Weather(R.drawable.weather_storm, "Storm"),
            new Weather(R.drawable.weather_sunny, "Sunny")
        };
 
        WeatherAdapter adapter = new WeatherAdapter(this, 
                R.layout.listview_item_row, weather_data);
 
        listView1 = (ListView)findViewById(R.id.listView1);
 
        View header = (View)getLayoutInflater().inflate(R.layout.listview_header_row, null);
        listView1.addHeaderView(header);
 
        listView1.setAdapter(adapter);
    }
public class MainActivity extends Activity {

    private ListView listView1;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Weather weather_data[] = new Weather[]
        {
            new Weather(R.drawable.weather_cloudy, "Cloudy"),
            new Weather(R.drawable.weather_showers, "Showers"),
            new Weather(R.drawable.weather_snow, "Snow"),
            new Weather(R.drawable.weather_storm, "Storm"),
            new Weather(R.drawable.weather_sunny, "Sunny")
        };

        WeatherAdapter adapter = new WeatherAdapter(this, 
                R.layout.listview_item_row, weather_data);

        listView1 = (ListView)findViewById(R.id.listView1);

        View header = (View)getLayoutInflater().inflate(R.layout.listview_header_row, null);
        listView1.addHeaderView(header);

        listView1.setAdapter(adapter);
    }

Несколько вещей в MainActivity которые требуются для объяснения для вашего лучшего понимания. Первое замечание, мы создаем массив экземпляров класса Weather, а так же иконок и заголовков которые передаются в конструктор в качестве параметров. Далее, объект WeatherAdapter создан и файл разметки listview_item_row.xml id и объекты класса Weather в массиве передаются в конструктор. Еще один раз, мы используем Android Layout Inflator что бы заполнить файл разметки нашгего listview_header_row.xml, это обрабатывается как заголовок для View. Это будет передано как параметр метода addHeaderView. 

В конце мы передаем наш кастомный адаптер в метод setAdapter.

На этом мы готовы скомпилировать и запустить наш проект. Если все реализовано правильно, вы увидите следующий вывод:

Android_Custom_ListView_ArrayAdapter

 

Эта статья является переводом отсюда

9 Responses

  1. Алекс 13.08.2013 / 02:02

    Такой вопрос, а как динамически заполнить массив?

    1
    2
    3
    4
    5
    6
    7
    8
    
    Weather weather_data[] = new Weather[]
            {
                new Weather(R.drawable.weather_cloudy, "Cloudy"),
                new Weather(R.drawable.weather_showers, "Showers"),
                new Weather(R.drawable.weather_snow, "Snow"),
                new Weather(R.drawable.weather_storm, "Storm"),
                new Weather(R.drawable.weather_sunny, "Sunny")
            };
    Weather weather_data[] = new Weather[]
            {
                new Weather(R.drawable.weather_cloudy, "Cloudy"),
                new Weather(R.drawable.weather_showers, "Showers"),
                new Weather(R.drawable.weather_snow, "Snow"),
                new Weather(R.drawable.weather_storm, "Storm"),
                new Weather(R.drawable.weather_sunny, "Sunny")
            };
  2. Никита 05.01.2014 / 00:34

    А как при таком подходе реализовать обработку щелчка по пункту списка? Заранее спасибо

  3. Никита 05.01.2014 / 03:05

    С предыдущим вопросом я уже сам разобрался) А можете подсказать, как сделать так, чтобы при нажатии на пункт списка появлялось изображение на весь экран(своё для каждого пункта) в виде toast? Спасибо

    • Pyatnitsev 05.01.2014 / 14:00

      Toast не настраиваемая вещь, просто отображать картинку возможно, можно с использованием fragments, можно просто activity вызывать

  4. Никита 05.01.2014 / 16:22

    А как тогда сделать,чтобы для каждого пункта вызывалась своя уникальная Activity?

    • Pyatnitsev 06.01.2014 / 20:46

      Идея такая:
      Делаете OnItemClickLisener.
      Из него можно определить — какой item выбран в листе.
      Далее зная номер этого элемента обращаетесь к массиву (в примере weather_data[]) и обращаетесь по тому же индексу — можете получить картинку. Осталось вызвать Activity и передать ей индекс выбранного item’а, а внутри активити найти такую картинку в массиве и отобразить ее.

      Или мне кодом написать ? =)

  5. name 12.01.2014 / 14:20

    youListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView adapterView, View view, int i, long l) {
    //здесь ваш код i это номер выбранного элемента, отсчет от 0
    }
    });

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