Android. Jak zrobić wydajną listę z zawierającą elementy różnego typu przy pomocy RecyclerView. Cz.1

android-128Potrzebujemy wyświetlić listę zawierającą elementy różnego typu. Dla przykładu niech to będzie lista polecanych produktów sklepu na której mogą się znaleźć np. płyta DVD, książka i tp. Lista może też zawierać elementy nie będące produktami jak nagłówek listy czy separator sekcji. Każdy z tych elementów ma swój własny widok z layoutem różnym od pozostałych. Elementy te mają swoje modele danych, reprezentowane przez obiekty różnych klas. Lista może być długa, więc potrzebujemy zarządzania pamięcią jaki oferuje RecyclerView.

1. Główna aktywność aplikacji i jej layout.

Tworzymy nowy projekt w Android Studio wybierając puste Activity. Do swtorzonej aktywności z layoutem dołączamy kontrolkę typuRecyclerView

res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.mycorp.multielementslist.MainActivity
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/list_recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/title_text"
        android:scrollbarStyle="outsideOverlay"
        android:scrollbars="vertical" />

</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private RecyclerView listRecyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Fresco.initialize(this);

        setContentView(R.layout.activity_main);

        listRecyclerView = (RecyclerView) findViewById(R.id.list_recycler);
    }
}

Nie zapomnij dodać do pliku app/build.gradle w sekcji dependencies linijki:

implementation 'com.android.support:recyclerview-v7:+'

Do ładowania zdjęć okładek z sieci użyjemy biblioteki Fresco. Dzięki temu będziemy mieć obsłużone ładowanie, cache’owanie obrazków i wydajne zarządzanie pamięcią. Inicjalizację biblioteki należy umieścić przed wywołaniem setContentView inaczej otrzymamy błąd "Error inflating class com.facebook.drawee.view.SimpleDraweeView". Do pliku app/build.gradle dodajemy oczywiście zależność od tej biblioteki:

compile 'com.facebook.fresco:fresco:1.7.1'

2. Klasy elementów.

Stwórzmy teraz modele reprezentujące dane potrzebnych elementów, czyli książki, płyty DVD oraz nagłówka listy:

model/Book.java

public class Book {

    private String title;
    private String author;
    private String coverUrl;

    public Book(String title, String author, String coverUrl) {
        this.title = title;
        this.author = author;
        this.coverUrl = coverUrl;
    }

    // here getters and setters
    // ...
}

model/DVD.java

public class Dvd {

    private String title;
    private String description;
    private String coverUrl;

    public Dvd(String title, String description, String coverUrl) {
        this.title = title;
        this.description = description;
        this.coverUrl = coverUrl;
    }

    // ...
}

model/DVD.java

public class Header {

    private String title;
    private String subTitle;

    public Header(String title, String subTitle) {
        this.title = title;
        this.subTitle = subTitle;
    }

    // ...
}

3. Klasa reprezentująca element listy.

Klasa reprezentująca element listy będzie niejako kontenerem na obiekty modelów z poprzedniego punktu. Będzie zawierać typ przechowywanego obiektu zdefiniowany w enumeratorze ItemType. Na potrzeby naszego przykładu powstały trzy typy: HEADER, BOOK, DVD. Dzięki temu adapter będzie wiedzieć, jakiego widoku użyć do wyrenderowania danego elementu.

RecycledListItem.java

public class RecycledListItem {

    public enum ItemType {
        HEADER, BOOK, DVD
    }

    private ItemType type;
    private Header header;
    private Book book;
    private Dvd dvd;

    public RecycledListItem(Header haeder) {
        this.header = header;
        this.type = ItemType.HEADER;
    }

    public RecycledListItem(Book book) {
        this.book = book;
        this.type = ItemType.BOOK;
    }

    public RecycledListItem(Dvd book) {
        this.dvd = book;
        this.type = ItemType.DVD;
    }

    public ItemType getType() {
        return type;
    }

    public Object getItem() {
        if (ItemType.HEADER.equals(this.type)) {
            return this.header;
        } else if (ItemType.BOOK.equals(this.type)) {
            return this.book;
        } else if (ItemType.DVD.equals(this.type)) {
            return this.dvd;
        } else {
            return null;
        }
    }
}

Przeciążyliśmy konstruktor, aby można było tworzyć kontener dla różnych klas modeli. Metodą getItem pobierzemy obiekt właściwego typu.

4. Widoki elementów

Zaprojektujemy teraz layouty dla poszczególnych elementów.

res/layout/header_item_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/box_with_shadow"
    android:padding="10dp"
    android:layout_margin="10dp">

        <TextView
            android:id="@+id/dvd_title_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="25sp"
            android:textAllCaps="true"
            android:textStyle="bold"
            android:text="Section header" />
</FrameLayout>

res/layout/dvd_item_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:fresco="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/box_with_shadow"
    android:padding="15dp"
    android:layout_margin="10dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.facebook.drawee.view.SimpleDraweeView
            android:id="@+id/dvd_cover_drawee"
            android:layout_width="120dp"
            android:layout_height="160dp"
            fresco:placeholderImage="@drawable/dvd"
            android:layout_marginRight="10dp"
            android:scaleType="centerCrop" />

        <TextView
            android:id="@+id/dvd_title_text"
            android:layout_toRightOf="@+id/dvd_cover_drawee"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="25sp"
            android:layout_marginBottom="3dp"
            android:text="Star Wars ep. IV" />

        <TextView
            android:id="@+id/dvd_description_text"
            android:layout_toRightOf="@+id/dvd_cover_drawee"
            android:layout_below="@+id/dvd_title_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:textSize="12sp"
            android:text="A young boy from Tatooine sets out on an adventure with an old Jedi named Obi-Wan Kenobi as his mentor to save Princess Leia." />

        <Button
            android:id="@+id/book_download_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@+id/dvd_cover_drawee"
            android:layout_below="@+id/dvd_description_text"
            android:textAllCaps="false"
            android:text="Watch trailer"/>

    </RelativeLayout>
</FrameLayout>

res/layout/book_item_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:fresco="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/box_with_shadow"
    android:padding="15dp"
    android:layout_margin="10dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.facebook.drawee.view.SimpleDraweeView
            android:id="@+id/book_cover_drawee"
            android:layout_width="120dp"
            android:layout_height="160dp"
            fresco:placeholderImage="@drawable/book"
            android:layout_marginLeft="10dp"
            android:layout_alignParentRight="true"
            android:scaleType="centerCrop" />

        <TextView
            android:id="@+id/book_title_text"
            android:layout_toLeftOf="@+id/book_cover_drawee"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="25sp"
            android:layout_marginBottom="3dp"
            android:layout_alignParentLeft="true"
            android:text="Lord of The Rings" />

        <TextView
            android:id="@+id/book_author_text"
            android:layout_toLeftOf="@+id/book_cover_drawee"
            android:layout_below="@+id/book_title_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_alignParentLeft="true"
            android:textSize="18sp"
            android:text="J. R. R. Tolkien" />

        <TextView
            android:id="@+id/book_description_text"
            android:layout_toLeftOf="@+id/book_cover_drawee"
            android:layout_below="@+id/book_author_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_alignParentLeft="true"
            android:textSize="12sp"
            android:text="A young Hobbit named Frodo is thrown on an amazing adventure, when he is appointed the job of destroying the one ring which was created by the dark lord Sauron." />

    </RelativeLayout>
</FrameLayout>

Elementy będą miały wygląd kafelków z zaokrąglonymi rogami oraz cieniem, należy więc zdefiniować ich kształt:
res/drawable/box_with_shadow/item_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="#CABBBBBB"/>
            <corners android:radius="5dp" />
        </shape>
    </item>

    <item
        android:left="0dp"
        android:right="0dp"
        android:top="0dp"
        android:bottom="2dp">
        <shape android:shape="rectangle">
            <solid android:color="@android:color/white"/>
            <corners android:radius="5dp" />
        </shape>
    </item>
</layer-list>

Umieśćmy też testowo obrazki okładek w res/drawable: book.jpg oraz dvd.jpg. Podczas docelowego ładowania obrazków z sieci powinna wyświetlać się też zaślepka: placeholder.png. Docelowo podmień w tagach SimpleDraweeView testowe obrazki na tę zaślepkę:

        <com.facebook.drawee.view.SimpleDraweeView
            ...
            fresco:placeholderImage="@drawable/book"
            ...
        />

Sprawdźmy, jak będą prezentować się nasze elementy. Dodajmy je tymczasowo do głównego layoutu, powyżej tagu :

    ...

    <include layout="@layout/header_item_layout"/>
    <include layout="@layout/dvd_item_layout"/>
    <include layout="@layout/book_item_layout"/>

    <android.support.v7.widget.RecyclerView
 
     ...

Powinniśmy otrzymać następujący ekran:

Na ten moment mamy zdefiniowane elementy, ich modele i layouty. W następnej części artykułu dowiemy się czym jest ViewHolder, oraz jak stworzyć listę naszych elementów aby renderowały się w kontrolce RecyclerView.

Cały kod źródłowy dostępny jest na GitHubie.

Podziel się:
Facebooktwittergoogle_plusredditpinterestlinkedintumblrmail

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *