2 komentarze

ListView, pierwsze kroki

Lipiec 15, 2011 Pierwsze kroki Tutoriale UI

W dzisiejszym artykule zagłębimy się w kolejny element interfejsu użytkownika, jakim jest ListView. Jego zastosowanie jest nadzwyczaj proste – wyświetlić zbiór obiektów jeden pod drugim. I tym też za chwilę się zajmiemy. Poznamy podstawy tworzenia widoku listy, spersonalizujemy jej wygląd i przyjrzymy się mechanizmom z nią związanym.

Przygotowanie

Standardowo, przed rozpoczęciem powinniśmy stworzyć nowy projekt Androidowy w środowisku Eclipse. W niniejszym artykule będzie on nosił nazwę HelloListView.
Zadaniem naszej aplikacji będzie wyświetlenie listy języków, które po kliknięciu wyświetlą krótką informację o tym jak w danym języku wygląda słowo „Cześć”. :)

Tablica napisów

Jako, że w naszym przykładzie źródłem danych będą zwykłe tablice napisów, na miejsce ich przechowywania wybierzemy plik /res/values/strings.xml. Służy on bowiem nie tylko do zapisywania pojedynczych słów/zdań, ale również do przechowywania całych tablic napisów. Element, który przechowuje takie dane to <string-array name=”…”>, wewnątrz którego zawarte są elementy <item>. Każdy <item> to pojedynczy element tablicy. Atrybut name elementu string-array jest nazwą tablicy. Więcej na ten temat możemy poczytać w dokumentacji (link).

Oto cała zawartość pliku strings.xml, który na razie będzie przechowywał dwie tablice napisów – listę języków (tablica languages) oraz listę odmian słowa „cześć” (hello_phrases).

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, MainActivity!</string>
    <string name="app_name">HelloListView</string>
    <string name="app_header">Multilingual \"Hello\"</string>
    <string name="hint">Tap language name to show its \"Hello\" phrase.</string>
    <string-array name="languages">
		<item>Albanian</item>
		<item>English</item>
		<item>Arabic</item>
		<item>Bulgarian</item>
		<item>Chinese</item>
		<item>Czech</item>
		<item>Danish</item>
		<item>Esperanto</item>
		<item>Estonian</item>
		<item>Filipino</item>
		<item>Finnish</item>
		<item>French</item>
		<item>Greek</item>
		<item>Spanish</item>
		<item>Dutch</item>
		<item>Icelandic</item>
		<item>Irish</item>
		<item>Japanese</item>
		<item>Korean</item>
		<item>Latin</item>
		<item>German</item>
		<item>Norwegian</item>
		<item>Portuguese</item>
		<item>Polish</item>
		<item>Russian</item>
		<item>Romanian</item>
		<item>Serbian</item>
		<item>Swedish</item>
		<item>Slovak</item>
		<item>Slovenian</item>
		<item>Turkish</item>
		<item>Ukrainian</item>
		<item>Hungarian</item>
		<item>Italian</item>
    </string-array>
    <string-array name="hello_phrases">
		<item>Tungjatjeta</item>
		<item>Hello</item>
		<item>Marhaba</item>
		<item>Zdrawejte</item>
		<item>Neih hou</item>
		<item>Nazdar</item>
		<item>Goddag</item>
		<item>Saluton</item>
		<item>Tervist</item>
		<item>Huy</item>
		<item>Paivaa</item>
		<item>Salut</item>
		<item>Geia siou</item>
		<item>Hola</item>
		<item>Hallo</item>
		<item>Godan</item>
		<item>Haileo</item>
		<item>Konnichi wa</item>
		<item>Annyong</item>
		<item>Salve</item>
		<item>Gruess dich</item>
		<item>Goddag</item>
		<item>Oi</item>
		<item>Cześć</item>
		<item>Priwiet</item>
		<item>Buna</item>
		<item>Zdravo</item>
		<item>Hejsan</item>
		<item>Ahoj</item>
		<item>Zhivjo</item>
		<item>Selam</item>
		<item>Prywit</item>
		<item>Szervusz</item>
		<item>Ciao</item>
    </string-array>
</resources>

Podstawy ListView

UI

Dodanie elementu ListView do naszego UI skutkuje dodaniem poniższych kilku linijek kodu:

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

Jak widać, nie ma tu nic nietypowego, dlatego od razu przejdziemy do kodu źródłowego naszej aplikacji.

Kod źródłowy aplikacji

Rozpoczynamy jak zwykle od dodania elementu layoutu do klasy Aktywności, w której będzie on wykorzystany:

...
public class MainActivity extends Activity {
	private ListView lvLanguages;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		lvLanguages = (ListView) findViewById(R.id.lvLanguages);
	}
}

Pobieranie danych z /res/values/strings.xml

Następnym krokiem będzie pobranie danych, które zapisaliśmy w pliku strings.xml. Odbywa się to poprzez obiekt Resources, który wewnątrz klasy Aktywności można pobrać za pomocą getResources(). Metoda ta zawraca obiekt dający nam dostęp do wszystkich zasobów folderu /res/ naszej aplikacji.

...
public class MainActivity extends Activity {
	private ListView lvLanguages;
	private String[] languages;
	private String[] helloPhrases;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		lvLanguages = (ListView) findViewById(R.id.lvLanguages);
		initResources();
	}

	private void initResources() {
		Resources res = getResources();
		languages = res.getStringArray(R.array.languages);
		helloPhrases = res.getStringArray(R.array.hello_phrases);
	}
}

Poszczególne elementy zasobów naszej aplikacji pobieramy za pomocą metod get…(int). Zwracają one element, na którego ID wskazują pola statyczne automatycznie generowanej klasy R. My jak widać korzystamy jedynie z metody getStringArray(int), której działanie jest chyba oczywiste. :)

Inicjalizacja ListView

Aby element ListView wyświetlił przekazane mu dane, musimy skorzystać z wzorca Adapter, który w tym wypadku powinien dziedziczyć z klasy BaseAdapter. Android domyślnie posiada dwie implementacje adapterów – CursorAdapter oraz ArrayAdapter. My w dzisiejszym artykule wykorzystamy ten drugi, który służy do „adaptowania” danych będących tablicami (CursorAdapter służy do obsługi danych bazodanowych).

...
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		lvLanguages = (ListView) findViewById(R.id.lvLanguages);
		initResources();
		initLanguagesListView();
	}

	private void initResources() {
		Resources res = getResources();
		languages = res.getStringArray(R.array.languages);
		helloPhrases = res.getStringArray(R.array.hello_phrases);
	}

	private void initLanguagesListView() {
		lvLanguages.setAdapter(new ArrayAdapter<String>(
				getApplicationContext(),
				android.R.layout.simple_list_item_1,
				languages));
	}
}

Za pomocą metody setAdapter() stworzyliśmy nowy obiekt ArrayAdapter. W tym wypadku skorzystaliśmy z konstruktora, któremu oprócz kontekstu aplikacji przekazujemy:

  • layout pojedynczego elementu listy (android.R.layout jest polem, w którym przechowywane są domyślne layouty, dostarczone wraz z systemem Android),
  • tablicę napisów, którą wcześniej pobraliśmy z zasobów naszej aplikacji.

Listę wszystkich dostępnych konstruktorów ArrayAdapter możemy znaleźć w dokumentacji (link).

Obsługa kliknięć na konkretny element listy

Na koniec dodamy jeszcze obsługę kliknięć na poszczególne elementy listy. Nie jest to nic trudnego, bowiem na obiekcie ListView możemy ustawić listener nasłuchujący kliknięć na jego elementach:

...
	private void initLanguagesListView() {
		lvLanguages.setAdapter(new ArrayAdapter<String>(
				getApplicationContext(),
				android.R.layout.simple_list_item_1,
				languages));

		lvLanguages.setOnItemClickListener(new OnItemClickListener() {
			public void onItemClick(AdapterView<?> parent, View v, int pos,	long id) {
				Toast.makeText(getApplicationContext(),
						helloPhrases[pos],
						Toast.LENGTH_SHORT).show();
			}
		});
	}

Interfejs OnItemClickListener() dostarcza nam metody onItemClick(), której argumenty pozwalają dokładnie określić na jaki element właśnie kliknęliśmy.
Jak widać, w naszej aplikacji skorzystaliśmy z obiektu Toast, który zgodnie z wybraną pozycją, wyświetli nam tekst z tablicy helloPhrases, przechowującej odmiany zwrotu „Cześć”.

Działanie i kompletny kod aplikacji

Kompletny kod źródłowy naszej aplikacji aktualnie składa się z:

MainActivity.java

package pl.froger.hellolistview;

import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MainActivity extends Activity {
	private ListView lvLanguages;
	private String[] languages;
	private String[] helloPhrases;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		lvLanguages = (ListView) findViewById(R.id.lvLanguages);
		initResources();
		initLanguagesListView();
	}

	private void initResources() {
		Resources res = getResources();
		languages = res.getStringArray(R.array.languages);
		helloPhrases = res.getStringArray(R.array.hello_phrases);
	}

	private void initLanguagesListView() {
		lvLanguages.setAdapter(new ArrayAdapter<String>(
				getApplicationContext(),
				android.R.layout.simple_list_item_1,
				languages));

		lvLanguages.setOnItemClickListener(new OnItemClickListener() {
			public void onItemClick(AdapterView<?> parent, View v, int pos,	long id) {
				Toast.makeText(getApplicationContext(),
						helloPhrases[pos],
						Toast.LENGTH_SHORT).show();
			}
		});
	}
}

/res/layout/main.xml

<?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="wrap_content">
	<TextView
		android:textAppearance="?android:attr/textAppearanceLarge"
		android:id="@+id/tvTitle"
		android:text="@string/app_header"
		android:layout_width="fill_parent"
		android:gravity="center"
		android:layout_height="wrap_content"></TextView>
	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:id="@+id/tvHint"
		android:text="@string/hint"></TextView>
	<ListView
		android:layout_height="fill_parent"
		android:layout_width="fill_parent"
		android:id="@+id/lvLanguages"></ListView>
</LinearLayout>

/res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, MainActivity!</string>
    <string name="app_name">HelloListView</string>
    <string name="app_header">Multilingual \"Hello\"</string>
    <string name="hint">Tap language name to show its \"Hello\" phrase.</string>
    <string-array name="languages">
		<item>Albanian</item>
		<item>English</item>
		<item>Arabic</item>
		<item>Bulgarian</item>
		<item>Chinese</item>
		<item>Czech</item>
		<item>Danish</item>
		<item>Esperanto</item>
		<item>Estonian</item>
		<item>Filipino</item>
		<item>Finnish</item>
		<item>French</item>
		<item>Greek</item>
		<item>Spanish</item>
		<item>Dutch</item>
		<item>Icelandic</item>
		<item>Irish</item>
		<item>Japanese</item>
		<item>Korean</item>
		<item>Latin</item>
		<item>German</item>
		<item>Norwegian</item>
		<item>Portuguese</item>
		<item>Polish</item>
		<item>Russian</item>
		<item>Romanian</item>
		<item>Serbian</item>
		<item>Swedish</item>
		<item>Slovak</item>
		<item>Slovenian</item>
		<item>Turkish</item>
		<item>Ukrainian</item>
		<item>Hungarian</item>
		<item>Italian</item>
    </string-array>
    <string-array name="hello_phrases">
		<item>Tungjatjeta</item>
		<item>Hello</item>
		<item>Marhaba</item>
		<item>Zdrawejte</item>
		<item>Neih hou</item>
		<item>Nazdar</item>
		<item>Goddag</item>
		<item>Saluton</item>
		<item>Tervist</item>
		<item>Huy</item>
		<item>Paivaa</item>
		<item>Salut</item>
		<item>Geia siou</item>
		<item>Hola</item>
		<item>Hallo</item>
		<item>Godan</item>
		<item>Haileo</item>
		<item>Konnichi wa</item>
		<item>Annyong</item>
		<item>Salve</item>
		<item>Gruess dich</item>
		<item>Goddag</item>
		<item>Oi</item>
		<item>Cześć</item>
		<item>Priwiet</item>
		<item>Buna</item>
		<item>Zdravo</item>
		<item>Hejsan</item>
		<item>Ahoj</item>
		<item>Zhivjo</item>
		<item>Selam</item>
		<item>Prywit</item>
		<item>Szervusz</item>
		<item>Ciao</item>
    </string-array>
</resources>

Jeśli wszystko zrobiliśmy tak jak należy, po uruchomieniu naszej aplikacji powinniśmy ujrzeć coś takiego:

Własny adapter danych dla ListView

Oprócz dostarczonych przez system ArrayAdapter oraz CursorAdapter, Android pozwala nam na tworzenie i wykorzystywanie własnych adapterów danych. Dzięki temu mamy możliwość wypełniania ListView w sposób całkowicie zdefiniowany przez nas, co daje naprawdę ogromne możliwości. I tym też teraz się zajmiemy.

Wygląd pojedynczego elementu listy

W poprzednim przykładzie wykorzystaliśmy jeden z gotowych stylów pojedynczych elementów listy, który zapisany jest pod stałą android.R.layout.simple_list_item_1. Teraz spróbujemy zbudować coś nieco bardziej skomplikowanego.

Zaczniemy od dodania nowego pliku z layoutem do /res/layout/. Nasz wygląd będzie zawierał dwa pola tekstowe i będzie wyglądał mniej więcej tak:

Oto cały kod źródłowy (plik: /res/layout/custom_list_item.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="horizontal"
	android:padding="15dp">
	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="0"
		android:height="20dp"
		android:width="20dp"
		android:gravity="center"
		android:background="@color/item_number_bg_color"
		android:textColor="@color/item_number_font_color"
		android:id="@+id/tvItemNumber"></TextView>
	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:id="@+id/tvLanguage"
		android:text="---"
		android:layout_marginLeft="20dp"></TextView>
</LinearLayout>

Zaznaczyłem w nim kolejną ciekawostkę. Jak widać bowiem, oprócz napisów, Android pozwala nam również na przechowywanie innych wartości wewnątrz folderu /res/values/. Do ciekawszych należą m.in. wielkości (Dimension), identyfikatory (stąd ciąg …@+id/…), itp. Kompletną listę można znaleźć w dokumentacji (link).

My wykorzystujemy kolory, które zapisane są jako /res/values/colors.xml. Kod źródłowy pliku jest krótki i wymowny :) :

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="item_number_bg_color">#ECECEC</color>
  	<color name="item_number_font_color">#000000</color>
</resources>

Kod adaptera

Kiedy zdefiniowaliśmy wygląd pojedynczych elementów naszej listy, pozostało nam już tylko napisanie naszego własnego adaptera dostarczanych danych.
Na razie pójdziemy na łatwiznę, przez co typem jaki przyjmuje nasz adapter będzie tablica stringów. Nic jednak nie stoi na przeszkodzie, by nasz adapter obsługiwał dane z klas modelowych. Wydaje mi się, że po zapoznaniu się z poniższym kodem nikt nie będzie miał wątpliwości w jaki sposób to zrobić. :)

Pierwszą rzeczą, którą powinniśmy zrobić jest stworzenie nowej klasy dziedziczącej po ArrayAdapter. W naszym przypadku ustawiamy od razu typ danych na String.

...
public class LanguagesArrayAdapter extends ArrayAdapter<String> {
	private Activity context;
	private String[] languages;

	public LanguagesArrayAdapter(Activity context, String[] languages) {
		super(context, R.layout.custom_list_item, languages);
		this.context = context;
		this.languages = languages;
	}	
}

Jak widać w wywołaniu konstruktora nadklasy, jako jeden z argumentów przekazujemy wygląd stworzony przez nas (poprzez wartość R.layout.custom_list_item).
Należy również zwrócić uwagę na to, że zamiast obiektu kontekstu przekazywany jest obiekt Aktywności. Uprości nam to nieco kod, który napiszemy za chwilę.

Przechodzimy teraz do rzeczy najważniejszej, czyli implementacji metody zwracającej gotowy element listy.

...
public class LanguagesArrayAdapter extends ArrayAdapter<String> {
	private Activity context;
	private String[] languages;

	public LanguagesArrayAdapter(Activity context, String[] languages) {
		super(context, R.layout.custom_list_item, languages);
		this.context = context;
		this.languages = languages;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		LayoutInflater layoutInflater = context.getLayoutInflater();
		View rowView =  layoutInflater.inflate(R.layout.custom_list_item, null, true);

		TextView tvLanguage = (TextView) rowView.findViewById(R.id.tvLanguage);
		TextView tvItemNumber = (TextView) rowView.findViewById(R.id.tvItemNumber);

		tvItemNumber.setText(Integer.toString(position));
		tvLanguage.setText(languages[position]);

		return rowView;
	}	
}

Metoda getView(…), zaimplementowana powyżej odpowiedzialna jest za zwrócenie obiektu View, który znajduje się na konkretnej pozycji naszej listy. Innymi słowy zwraca ona „widok” poszczególnych elementów listy.

W zasadzie w tym momencie moglibyśmy zakończyć ten artykuł gdyby nie mały szczegół…

Optymalizacja

W związku z naszym przykładem punkt ten nie będzie miał większego znaczenia. Jednak należy pamiętać, że w realnych aplikacjach element ListView bardzo często wyświetla po kilkadziesiąt/kilkaset elementów o dużo bardziej skomplikowanej budowie. Dlatego też należy zaznajomić się z kilkoma kwestiami, które pozwolą zwiększyć wydajność naszej listy niemal trzykrotnie (przy dużych zbiorach danych).

Tworzenie obiektów

Pierwszym wydajnościowym problemem naszego przykładu jest tworzenie nowego obiektu Javowego dla każdego elementu naszej listy. Jest to nierozsądne dlatego, że obiekt ten wykorzystywany jest tylko w momencie przekazania widoku elementu do wyświetlenia. Co się dzieje później? Działa Garbage Collector po czym kolejny obiekt listy jest tworzony. I tak w kółko.

Można jednak proces ten nieco przyspieszyć. Odbywa się to dzięki „recyklingowi” obiektów listy, który pozwala na wykorzystanie jednego obiektu do wyświetlenia wszystkich elementów listy. Jak to wygląda w praktyce? Tak:

	...
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		View rowView = convertView;
		if(rowView == null){
			LayoutInflater layoutInflater = context.getLayoutInflater();
			rowView =  layoutInflater.inflate(R.layout.custom_list_item, null, true);
		}

		TextView tvLanguage = (TextView) rowView.findViewById(R.id.tvLanguage);
		TextView tvItemNumber = (TextView) rowView.findViewById(R.id.tvItemNumber);

		tvItemNumber.setText(Integer.toString(position));
		tvLanguage.setText(languages[position]);

		return rowView;
	}

Jednym z parametrów metody getView(…) jest obiekt convertView (typu View). Jest on właśnie tym odzyskanym obiektem widoku, który możemy wykorzystać do ponownego wyświetlenia.
Zatem, jeśli obiekt ten jest nullem, widok musimy stworzyć we własnym zakresie. Jeżeli jednak obiekt już istnieje, możemy go z powodzeniem wykorzystać do wyświetlenia następnych elementów listy.

Eliminacja czasochłonnych metod

Drugim wydajnościowym problemem, którym powinniśmy się zająć jest niepotrzebne wielokrotne wywoływanie metody findViewById(…). Jest ona bowiem bardzo zasobożerna, a jak widać wykorzystujemy ją dwukrotnie przy każdym wywołaniu metody getView(…) (czyli przy każdym elemencie naszej listy).
Powinniśmy zatem zastanowić się co zrobić aby ograniczyć ilość wywołań metody findViewById(…) do niezbędnego minimum (czyli najlepiej do wywołania tylko w momencie tworzenia nowego obiektu widoku).
Z pomocą przychodzi nam tutaj wzorzec ViewHolder, którego implementacja wygląda w ten sposób:

...
public class LanguagesArrayAdapter extends ArrayAdapter<String> {
	private Activity context;
	private String[] languages;

	public LanguagesArrayAdapter(Activity context, String[] languages) {
		super(context, R.layout.custom_list_item, languages);
		this.context = context;
		this.languages = languages;
	}

	static class ViewHolder {
		public TextView tvLanguage;
		public TextView tvItemNumber;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder viewHolder;
		View rowView = convertView;
		if(rowView == null) {
			LayoutInflater layoutInflater = context.getLayoutInflater();
			rowView = layoutInflater.inflate(R.layout.custom_list_item, null, true);
			viewHolder = new ViewHolder();
			viewHolder.tvLanguage = (TextView) rowView.findViewById(R.id.tvLanguage);
			viewHolder.tvItemNumber = (TextView) rowView.findViewById(R.id.tvItemNumber);
			rowView.setTag(viewHolder);
		} else {
			viewHolder = (ViewHolder) rowView.getTag();
		}
		viewHolder.tvItemNumber.setText(Integer.toString(position));
		viewHolder.tvLanguage.setText(languages[position]);
		return rowView;
	}
}

Oto co zrobiliśmy:

  • linijki 20-23 – Stworzyliśmy nową zagnieżdżoną klasę statyczną (pod tym adresem można znaleźć bardzo konkretny opis czym jest taka konstrukcja), która będzie nam służyła do przechowywania poszczególnych elementów UI.
  • linijki 32-35 – za pomocą metod findViewById(…) (które wywoływane są tylko raz – w momencie tworzenia nowego obiektu widoku), pobieramy elementy UI do ViewHoldera. Obiekt ten przekazujemy przez setTag(…) do naszego widoku. Metoda ta pozwala nam na dołączanie „załączników” do obiektu widoku.
  • linijka 37 – kiedy „odzyskujemy” obiekt widoku, wystarczy, że pobierzemy z niego wstawiony przez nas „załącznik” w postaci ViewHoldera.
  • linijki 39,40 – operujemy na elementach UI, na które wskazuje nasz ViewHolder.

I to wszystko w temacie optymalizacji. Tym sposobem udało nam się przyspieszyć element ListView niemal trzykrotnie. :)

Screeny

Oto efekty naszej pracy z niestandardowym adapterem listy:

Kompletny kod źródłowy

MainActivity.java

package pl.froger.hellolistview;

import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.Toast;

public class MainActivity extends Activity {
	private ListView lvLanguages;
	private String[] languages;
	private String[] helloPhrases;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		lvLanguages = (ListView) findViewById(R.id.lvLanguages);
		initResources();
		initLanguagesListView();
	}

	private void initResources() {
		Resources res = getResources();
		languages = res.getStringArray(R.array.languages);
		helloPhrases = res.getStringArray(R.array.hello_phrases);
	}

	private void initLanguagesListView() {
		LanguagesArrayAdapter adapter = new LanguagesArrayAdapter(this, languages);
		lvLanguages.setAdapter(adapter);

		lvLanguages.setOnItemClickListener(new OnItemClickListener() {
			public void onItemClick(AdapterView<?> parent, View v, int pos,	long id) {
				Toast.makeText(getApplicationContext(), 
						helloPhrases[pos],
						Toast.LENGTH_SHORT).show();
			}
		});
	}
}

LanguagesArrayAdapter.java

package pl.froger.hellolistview;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class LanguagesArrayAdapter extends ArrayAdapter<String> {
	private Activity context;
	private String[] languages;

	public LanguagesArrayAdapter(Activity context, String[] languages) {
		super(context, R.layout.custom_list_item, languages);
		this.context = context;
		this.languages = languages;
	}

	static class ViewHolder {
		public TextView tvLanguage;
		public TextView tvItemNumber;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder viewHolder;
		View rowView = convertView;
		if(rowView == null) {
			LayoutInflater layoutInflater = context.getLayoutInflater();
			rowView = layoutInflater.inflate(R.layout.custom_list_item, null, true);
			viewHolder = new ViewHolder();
			viewHolder.tvLanguage = (TextView) rowView.findViewById(R.id.tvLanguage);
			viewHolder.tvItemNumber = (TextView) rowView.findViewById(R.id.tvItemNumber);
			rowView.setTag(viewHolder);
		} else {
			viewHolder = (ViewHolder) rowView.getTag();
		}
		viewHolder.tvItemNumber.setText(Integer.toString(position));
		viewHolder.tvLanguage.setText(languages[position]);
		return rowView;
	}
}

main.xml

<?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="wrap_content">
	<TextView
		android:textAppearance="?android:attr/textAppearanceLarge"
		android:id="@+id/tvTitle"
		android:text="@string/app_header"
		android:layout_width="fill_parent"
		android:gravity="center"
		android:layout_height="wrap_content"></TextView>
	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:id="@+id/tvHint"
		android:text="@string/hint"></TextView>
	<ListView
		android:layout_height="fill_parent"
		android:layout_width="fill_parent"
		android:id="@+id/lvLanguages"></ListView>
</LinearLayout>

custom_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="horizontal"
	android:padding="15dp">
	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="0"
		android:height="20dp"
		android:width="20dp"
		android:gravity="center"
		android:background="@color/item_number_bg_color"
		android:textColor="@color/item_number_font_color"
		android:id="@+id/tvItemNumber"></TextView>
	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:id="@+id/tvLanguage"
		android:text="---"
		android:layout_marginLeft="20dp"></TextView>
</LinearLayout>

Cały projekt dostępny jest na naszym githubie – HelloListView.

Komentarze (2) Subskrybuj

 

  1. [...] widoku, jednokrotne wykonanie findViewById()). Artykuł z tym związany można przeczytać pod tym [...]

Prześlij komentarz

Zaloguj się lub skorzystaj z profilu:

[rpxlogin redirect="http://www.android4devs.pl" prompt="" style="large"]

Możesz również zostawić komentarz bez rejestracji, korzystając z poniższego formularza:

Musisz być zalogowany aby móc pisać komentarze.