Szkolenia programowania we Wrocławiu
Kurs Android

Własny widok listowy

Tak jak wspomniałem przy okazji ostatniego artykułu, dziś zajmiemy się bardziej złożonym przykładem wykorzystania komponentu ListView, a mianowicie napiszemy swój własny adapter. Pozwala to na stworzenie własnej definicji poszczególnych wierszy na liście.

1. Szkielety XML

Pomijam cały proces tworzenia projektu, bo jest to podstawa, którą już wszyscy umiemy. Natomiast jako pierwszy punkt prac przyjmijmy sobie stworzenie layoutu, w którym będzie przechowywana lista, oraz pliku XML zawierającego definicję pojedyńczego wiersza na liście.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#330066"
    tools:context=".Main" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="15dp"
        android:text="@string/hello_world"
        android:textColor="#FFF"
        android:textSize="20sp" />

    <ListView
        android:id="@+id/Lista"
        android:layout_marginTop="50dp"
        android:background="#FFF"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true" >

    </ListView>
</RelativeLayout>

w pliku main.xml nie ma nic, co mogło by nas zdziwić. Jedyna różnica w stosunku do tego z poprzedniego artykułu to to, że na szczycie listy dodałem sobie stały nagłówek.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_marginLeft="5dp"
    android:layout_marginRight="5dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView android:id="@+id/imgIcon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:gravity="center_vertical"
        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:paddingLeft="60dp"
        android:textStyle="bold"
        android:textSize="22sp"
        android:textColor="#000000"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp" />
</FrameLayout>

Również plik custom_row.xml nie ma przed nami żadnych tajemnic. Składa się on bowiem z obrazka i tekstu, które są odpowiednio sformatowane i opakowane w FrameLayout. Po więcej informacji o formatowaniu komponentów i tymże układzie zapraszam do dokumentacji.

2. Ziarno opisujące nasz wiersz - jest to ważny etap tego procesu. Ziarno to jakby taki model, opisujący dane zjawisko. Powinien zawierać pola opisujące jego właściwości i metody/konstruktory służące do ich ustawiania/zwracania. Jako przykład podam ziarno CustomerBean, sklepu internetowego. Zastanówmy się jakie podstawowe informacje potrzebne są sklepowi? Na pewno imię, nazwisko, adres, jakiś telefon. Tak więc klasa CustomerBean powinna zawierać takie właśnie pola, oraz metody/konstruktory pozwalające na operowanie nimi. Dzięki temu, pojedynczego klienta możemy traktować jako obiekt takiej klasy i odwrotnie - obiekt takiej klasy modeluje nam poszczególnego klienta.

public class RowBean {

    public int icon;
    public String title;

    public RowBean(){

    }

    public RowBean(int icon, String title) {

        this.icon = icon;
        this.title = title;
    }
}

W naszym przypadku taka klasa będzie zawierać String opisujący tytuł naszego wiersza, oraz int przechowujący dane o obrazku.

3. Własny adapter - najtrudniejsza część ćwiczenia - Jak wspominałem ostatnio, adapter jest to klasa służąca do połączenia naszej treści z komponentem ListView. W tym przypadku odbiegamy od standardowego modelu i będziemy musieli zrobić swój adapter. Poniżej postaram się wyjaśnić co bardziej skomplikowane fragmenty programu.

public class RowAdapter extends ArrayAdapter<RowBean>{

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

    public RowAdapter(Context context, int layoutResourceId, RowBean[] 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;
        RowBeanHolder holder = null;

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

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

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

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

        return row;
    }

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

Na pierwszy rzut oka widać, że przy tworzeniu własnego adaptera, głównie operujemy na widoku (View). Cały adapter zawiera konstruktor, metodę getView zwracającą nam widok wiersza, oraz statyczną klasę definiującą tzw. "trzymacza", uchwyt do wiersza.

Najistotniejszą częścią dla nas jest metoda getView, zwróćmy więc uwagę co się tam wyprawia. Główną istotą tej metody jest LayoutInflater. Jest to byt, który definiuje reprezentację wiersza na podstawie istniejących plików XML. Reprezentacja ta jest potem przypisywana polu row. Następnie nasz "trzymacz" otrzymuje dokładny adres, gdzie będą się znajdować obrazki i tytuły, czyli R.id.*; Na koniec na polu row ustawiany jest tag z naszym "trzymaczem" w argumencie. Tak "wyposażony" row jest zwracany.

Wydaje się to dosyć trudne i w istocie rzeczy, dosyć trudne jest. Więcej o tworzeniu adapterów możemy przeczytać tutaj.

4. Klasa Main - na koniec, kiedy już przebrnęliśmy przez adapter i layouty, pozostaje nam

public class Main extends Activity {

    private ListView listView1;

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

        RowBean RowBean_data[] = new RowBean[] {

        		new RowBean(R.drawable.samochod, "Mercedes"),
        		new RowBean(R.drawable.volks, "Volkswagen"),
        		new RowBean(R.drawable.bmww, "BMW"),
        		new RowBean(R.drawable.aston, "Aston Martin"),
        		new RowBean(R.drawable.samochod, "Mercedes"),
        		new RowBean(R.drawable.volks, "Volkswagen"),
        		new RowBean(R.drawable.bmww, "BMW"),
        		new RowBean(R.drawable.aston, "Aston Martin"),
        		new RowBean(R.drawable.samochod, "Mercedes"),
        		new RowBean(R.drawable.volks, "Volkswagen"),
        		new RowBean(R.drawable.bmww, "BMW"),
        		new RowBean(R.drawable.aston, "Aston Martin"),
        		new RowBean(R.drawable.samochod, "Mercedes"),
        		new RowBean(R.drawable.volks, "Volkswagen"),
        		new RowBean(R.drawable.bmww, "BMW"),
        		new RowBean(R.drawable.aston, "Aston Martin")
        };

        RowAdapter adapter = new RowAdapter(this,
                R.layout.custom_row, RowBean_data);

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

        listView1.setAdapter(adapter);
    }
}

Tutaj sprawa jest już prosta. Tworzymy sobie tablicę obiektów typu RowBean, każdemu w konstruktorze podając adres obrazka i tytuł. Uwaga - obrazek musi być już wcześniej zdeponowany w res/drawable.

W drugiej kolejności tworzymy obiekt naszego adaptera, któremu w konstruktorze podajemy adres naszego XMLa odpowiadającego za definicję wiersza, oraz tablicę obiektów typu RowBean.

W trzeciej kolejności przechwytujemy uchwyt do naszej listy.

Na koniec naszemu uchwytowi do listy trzeba ustawić adapter i to w zasadzie koniec.

Całość powinna prezentować się tak:

Komentarze

Khuukii

Można wiedzieć przypisać ClickListenera do poszczególnego przycisku w ListView?
I tak w ogóle to świetna robota :) Czekam na więcej porad.

EndOf

http://looksok.wordpress.com/tag/listview-item-with-button/

Majere

Super kurs!
Czekamy na więcej :)

Tomek

Jak dodać opcje wyszukiwania informacji z listy?

Han

Nie rozumiem po co są te trzy linie kodu w metodzie getView():
RowBean object = data[position];
holder.txtTitle.setText(object.title);
holder.imgIcon.setImageResource(object.icon);

Czy mógłby to ktoś wytłumaczyć?
Z góry dziękuję.

Pawel

Jak sprawić, by tablica obiektów była zmienna dynamicznie? Próbowałem ją zrobić:
RowBean RowBean_data[] = new RowBean[15]; a później dodawać wiersze:
RowBean_data[0] = new RowBean(R.drawable.samochod, "Mercedes");

ale powodowalo to wysypanie programu

Pawel

witam
Proszę o pomoc jak zrobić aby po kliknięciu na Mercedes otwierało się kolejne Activity Mercedes,Volkswagen itd.