Te ćwiczenia są częścią kursu Android Kotlin Fundamentals. Skorzystaj z tego kursu, jeśli będziesz wykonywać kolejno kilka ćwiczeń z programowania. Wszystkie ćwiczenia z kursu są wymienione na stronie docelowej ćwiczeń z programowania na temat Kotlin.
Wprowadzenie
Z tego ćwiczenia z programowania dowiesz się, jak używać listy RecyclerView
do wyświetlania list elementów. Korzystając z aplikacji do monitorowania snu z poprzedniej serii ćwiczeń z programowania, poznasz lepszy i bardziej uniwersalny sposób wyświetlania danych z zalecaną architekturą RecyclerView
.
Co musisz wiedzieć
Pamiętaj:
- Tworzenie podstawowego interfejsu użytkownika za pomocą aktywności, fragmentów i widoków danych.
- Przechodzenie między fragmentami i przekazywanie danych między nimi za pomocą
safeArgs
. - Korzystając z widoków modeli, możesz wyświetlać fabryki, przekształcenia oraz obiekty
LiveData
i ich obserwatorów. - Tworzenie bazy danych
Room
, tworzenie DAO i definiowanie jednostek. - Korzystanie z algorytmów baz danych i innych zadań długotrwałych.
Czego się nauczysz
- Aby użyć listy
RecyclerView
z elementamiAdapter
iViewHolder
, aby wyświetlić listę elementów.
Jakie zadania wykonasz:
- Zmień aplikację TrackMySleepQuality z poprzedniej lekcji, by korzystać z
RecyclerView
w celu wyświetlania danych o jakości snu.
W ramach tego ćwiczenia tworzysz część RecyclerView
aplikacji, która monitoruje jakość snu. Aplikacja używa bazy danych Room
do przechowywania danych o śnie.
Początkowa aplikacja do monitorowania snu ma 2 ekrany reprezentowane przez fragmenty, jak widać na ilustracji poniżej.
Pierwszy ekran (po lewej stronie) ma przyciski do włączania i wyłączania śledzenia. Na tym ekranie widoczne są również wszystkie dane dotyczące snu użytkownika. Kliknięcie przycisku Wyczyść powoduje trwałe usunięcie wszystkich danych użytkownika zbieranych przez aplikację. Na drugim ekranie (po prawej) możesz wybrać ocenę jakości snu.
Ta aplikacja używa uproszczonej architektury z kontrolerem interfejsu, ViewModel
i LiveData
. Aplikacja używa też bazy danych Room
, by trwałe dane o śnie.
Lista nocy snu widoczna na pierwszym ekranie działa, ale nie jest piękna. Aplikacja używa złożonego formatowania, aby tworzyć ciągi tekstowe na potrzeby widoku tekstu oraz liczby na potrzeby jakości. Ten projekt również nie jest skalowany. Gdy rozwiążesz te problemy z ćwiczeń z programowania, końcowa aplikacja uzyska te same funkcje, a ekran główny będzie wyglądał tak:
Wyświetlanie listy lub siatki danych to jedno z najczęstszych zadań Androida w interfejsie użytkownika. Listy mogą być bardzo łatwe lub bardzo złożone. Lista widoków tekstowych może zawierać proste dane, np. listę zakupów. Złożona lista, na przykład z adnotacjami na temat miejsc docelowych wakacji, może wyświetlać użytkownikowi wiele szczegółów w przewijanej siatce z nagłówkami.
Aby zapewnić działanie wszystkich tych zastosowań, Android ma widżet RecyclerView
.
Ogromną zaletą stosowania atrybutu RecyclerView
jest to, że jest bardzo wydajna w przypadku dużych list:
- Domyślnie
RecyclerView
przetwarza i rysuje tylko elementy, które są obecnie widoczne na ekranie. Jeśli na przykład lista zawiera tysiące elementów, ale widocznych jest tylko 10 elementów,RecyclerView
wystarczy, aby udało się narysować na ekranie 10 elementów. Gdy użytkownik przewija stronę,RecyclerView
znajduje informacje o nowych elementach na ekranie i wystarczająco dużo, by je wyświetlić. - Gdy element przewija się poza ekranem, następuje odświeżenie jego wyświetleń. Oznacza to, że element jest wypełniony nowymi treściami przewijanymi na ekran. To działanie
RecyclerView
pozwala zaoszczędzić czas potrzebny na przetwarzanie i ułatwia płynne przewijanie list. - Gdy element się zmieni, zamiast zmieniać jego całą listę,
RecyclerView
może zaktualizować ten element. To ogromny wzrost wydajności, gdy wyświetlasz listy złożonych elementów.
Jak widać, w sekwencji widać, że jeden widok został wypełniony danymi: ABC
. Po tym czasie RecyclerView
przewinie widok poza Twój ekran, aby użyć go ponownie: XYZ
.
Wzór adaptera
Jeśli podróżujesz między krajami, które korzystają z innych gniazdek elektrycznych, prawdopodobnie wiesz, jak podłączyć urządzenia do gniazdka za pomocą zasilacza. Adapter umożliwia konwertowanie jednego typu wtyczki na drugi, co powoduje konwertowanie jednego interfejsu na drugi.
Wzorzec dostosowania używany w inżynierii oprogramowania pomaga w obsłudze obiektu przy użyciu innego interfejsu API. RecyclerView
wykorzystuje adapter, aby przekształcić dane aplikacji w coś, co RecyclerView
może wyświetlić, bez zmiany sposobu przechowywania i przetwarzania danych przez aplikację. W przypadku aplikacji do monitorowania snu tworzysz adapter, który dostosowuje dane z bazy danych Room
do kodu, który RecyclerView
wie, jak wyświetlać, bez zmieniania elementu ViewModel
.
Wdrażanie RecyclerView
Aby wyświetlać dane w narzędziu RecyclerView
, potrzebujesz tych elementów:
- Dane do wyświetlenia.
- Instancja
RecyclerView
zdefiniowana w pliku układu, która będzie pełnić funkcję kontenera widoków danych. - Układ jednego elementu danych.
Jeśli wszystkie elementy listy wyglądają tak samo, możesz wykorzystać ten sam układ dla wszystkich, ale nie jest to wymagane. Układ elementu trzeba utworzyć oddzielnie od fragmentu, aby można było utworzyć widok danych elementu i wypełnić go danymi. - Menedżer układu.
Menedżer układu obsługuje organizację (układ) komponentów interfejsu w widoku danych. - Obiekt wyświetlania.
Powiększa on klasęViewHolder
. Zawiera on informacje o widoku dla wyświetlania jednego elementu z układu elementu. Właściciele widoków danych dodają też informacje, którychRecyclerView
używa do efektywnego przenoszenia widoków na ekran. - Adapter.
Adapter łączy dane zRecyclerView
. Dostosowuje on dane tak, by były wyświetlane w narzędziuViewHolder
.RecyclerView
używa adaptera, aby określić sposób wyświetlania danych na ekranie.
W tym zadaniu dodasz RecyclerView
do pliku układu i skonfigurujesz Adapter
, aby wyświetlić dane dotyczące snu w narzędziu RecyclerView
.
Krok 1. Dodaj RecyclerView z LayoutManager
W tym kroku zastąpisz ScrollView
ciągiem RecyclerView
w pliku fragment_sleep_tracker.xml
.
- Pobierz aplikację RecyclerViewFundamentals-Starter z GitHuba.
- Utwórz i uruchom aplikację. Zwróć uwagę na to, jak dane są wyświetlane w postaci zwykłego tekstu.
- Otwórz plik układu
fragment_sleep_tracker.xml
na karcie Projekt w Android Studio. - W panelu Drzewo komponentów usuń
ScrollView
. Ta czynność usuwa też elementTextView
, który jest wewnątrzScrollView
. - W panelu Paleta przewiń listę typów komponentów po lewej stronie, aby znaleźć opcję Kontenery, a potem ją wybierz.
- Przeciągnij
RecyclerView
z panelu Paleta do panelu Drzewo komponentów. UmieśćRecyclerView
w obiekcieConstraintLayout
.
- Jeśli pojawi się okno z pytaniem, czy chcesz dodać zależność, kliknij OK, aby zezwolić Android Studio na dodanie zależności
recyclerview
do pliku Gradle. Synchronizacja aplikacji może potrwać kilka sekund.
- Otwórz plik
build.gradle
modułu, przewiń na koniec i zanotuj nową zależność, która wygląda podobnie do tego kodu:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
- Wróć do
fragment_sleep_tracker.xml
. - Na karcie Tekst znajdź kod
RecyclerView
widoczny poniżej:
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" />
RecyclerView
id
otrzymujesleep_list
.
android:id="@+id/sleep_list"
- Umieść
RecyclerView
w obszarzeConstraintLayout
, aby zajmował pozostałą część ekranu. Aby to zrobić, przypisz górną granicęRecyclerView
do przycisku Start, na dole do przycisku Wyczyść i do obu stron elementu nadrzędnego. Ustaw szerokość i wysokość układu na 0 dp w Edytorze układu lub w formacie XML, korzystając z tego kodu:
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/clear_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/stop_button"
- Dodaj menedżera układu do pliku XML
RecyclerView
. Każdy elementRecyclerView
potrzebuje menedżera układu, który informuje go, jak umieścić elementy na liście. Android udostępniaLinearLayoutManager
, który domyślnie rozmieszcza elementy w pionowej liście pełnej szerokości wierszy.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- Przejdź na kartę Projekt i zauważ, że dodane ograniczenia spowodowały, że okno
RecyclerView
rozwinęło się w dostępnym miejscu.
Krok 2. Utwórz układ elementu listy i właściciela tekstu
RecyclerView
to tylko kontener. W tym kroku utworzysz układ i infrastrukturę elementów, które będą wyświetlane w obrębie obiektu RecyclerView
.
Aby jak najszybciej przejść do działającego obiektu RecyclerView
, użyj uproszczonej listy, która wyświetla tylko jakość snu w postaci liczby. Aby to zrobić, potrzebujesz właściciela widoku danych, TextItemViewHolder
. Musisz też mieć widok danych TextView
. W dalszej części dowiesz się więcej o posiadaczach widoków danych i o tym, jak rozłożyć wszystkie dane dotyczące snu.
- Utwórz plik układu o nazwie
text_item_view.xml
. Nie ma znaczenia, którego używasz jako elementu głównego, ponieważ zastąpisz kod szablonu. - W aplikacji
text_item_view.xml
usuń cały podany kod. - Dodaj
TextView
z dopełnieniem16dp
na początku i na końcu oraz rozmiar tekstu24sp
. Dopasuj szerokość do elementu nadrzędnego, a wysokość zawija zawartość. Ten widok jest wyświetlany wRecyclerView
, więc nie musisz go umieszczać w sekcjiViewGroup
.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:textSize="24sp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
- Otwórz aplikację
Util.kt
. Przewiń w dół i dodaj definicję podaną poniżej, co spowoduje utworzenie klasyTextItemViewHolder
. Umieść kod na końcu pliku po ostatnim nawiasie klamrowym. Kod zostanie przesłany w usłudzeUtil.kt
, ponieważ jest on tymczasowy i zastąpisz go później.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
- Jeśli pojawi się taka prośba, zaimportuj
android.widget.TextView
iandroidx.recyclerview.widget.RecyclerView
.
Krok 3. Utwórz adapter snu
Podstawowym zadaniem wdrożenia RecyclerView
jest utworzenie adaptera. Potrzebujesz prostego widoku dla widoku elementu i układu każdego elementu. Teraz możesz utworzyć adapter. Adapter tworzy uchwyt widoku danych i wypełnia go danymi, które RecyclerView
ma wyświetlić.
- W pakiecie
sleeptracker
utwórz nową klasę Kotlin o nazwieSleepNightAdapter
. - Rozszerz klasę
SleepNightAdapter
oRecyclerView.Adapter
. Klasa o nazwieSleepNightAdapter
służy do przekształcania obiektuSleepNight
w element, któregoRecyclerView
może używać. Adapter musi wiedzieć, którego uchwytu użyć, więc przekaż kartęTextItemViewHolder
. Gdy pojawi się prośba o zaimportowanie niezbędnych komponentów, wyświetli się błąd, ponieważ istnieją metody obowiązkowe.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
- Na najwyższym poziomie jednostki organizacyjnej
SleepNightAdapter
utwórz zmiennąlistOf
SleepNight
do przechowywania danych.
var data = listOf<SleepNight>()
- W polu
SleepNightAdapter
zastąpgetItemCount()
, by wyświetlić rozmiar listy nocy wdata
.RecyclerView
musi wiedzieć, ile elementów ma wyświetlać adapter, i wywołuje poleceniegetItemCount()
.
override fun getItemCount() = data.size
- W
SleepNightAdapter
zastąp funkcjęonBindViewHolder()
, jak pokazano poniżej.
FunkcjaonBindViewHolder()
jest wywoływana przez funkcjęRecyclerView
w celu wyświetlenia danych w jednym miejscu na liście w określonym miejscu. MetodaonBindViewHolder()
przyjmuje więc dwa argumenty: właściciel widoku i pozycję danych do powiązania. W przypadku tej aplikacji właścicielem jestTextItemViewHolder
, a pozycja jest na liście.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
- W
onBindViewHolder()
utwórz zmienną dla 1 produktu w danym miejscu w danych.
val item = data[position]
- Utworzony przez Ciebie element
ViewHolder
ma usługę o nazwietextView
. W ramachonBindViewHolder()
ustawtext
ztextView
liczby na jakość snu. Ten kod wyświetla tylko listę liczb, ale ten prosty przykład pokazuje, jak adapter importuje dane na wyświetlacz i na ekran.
holder.textView.text = item.sleepQuality.toString()
- W
SleepNightAdapter
zastępuj i wdrażaj elementonCreateViewHolder()
, który jest wywoływany, gdyRecyclerView
do wyświetlania elementu potrzebuje właściciela widoku.
Ta funkcja przyjmuje 2 parametry i zwracaViewHolder
. Parametrparent
, czyli grupa widoku, do której należy posiadacz widoku, to zawszeRecyclerView
. ParametrviewType
jest używany, gdy wiele elementów występuje w jednym elemencieRecyclerView
. Jeśli na przykład umieścisz listę widoków tekstu, obrazu i filmu w tym samym obiekcieRecyclerView
, funkcjaonCreateViewHolder()
będzie musiała określić, jakiego typu widoku należy użyć.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
- W
onCreateViewHolder()
utwórz instancjęLayoutInflater
.
W narzędziu do wypełnienia układów widać, jak można tworzyć widoki danych z układów XML.context
zawiera informacje o tym, jak prawidłowo zawyżać widok. W widoku z widoku z tagu recyklingu przekazujesz w kontekście grupy widoku danychparent
, czyliRecyclerView
.
val layoutInflater = LayoutInflater.from(parent.context)
- W
onCreateViewHolder()
utwórzview
, prosząclayoutinflater
o zwiększenie budżetu.
Przekaż układ XML dla tego widoku i grupę widoków danychparent
. Trzecim argumentem logicznym jestattachToRoot
. Ten argument musi mieć wartośćfalse
, ponieważRecyclerView
dodaje ten element do hierarchii widoków w chwili, gdy ma to miejsce.
val view = layoutInflater
.inflate(R.layout.text_item_view, parent, false) as TextView
- W:
onCreateViewHolder()
zwracajTextItemViewHolder
zview
.
return TextItemViewHolder(view)
- Adapter musi poinformować
RecyclerView
, gdydata
się zmieni, ponieważRecyclerView
nie wie nic o danych. Wiadomo tylko na temat właścicieli widoków danych, które otrzymał od adaptera.
Aby poinformowaćRecyclerView
, gdy wyświetlane są dane, dodaj niestandardowe ustawienie do zmiennejdata
, która znajduje się u góry klasySleepNightAdapter
. W konfiguracji ustaw nową wartośćdata
, a następnie wywołaj metodęnotifyDataSetChanged()
, aby wznowić tworzenie listy z nowymi danymi.
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}
Krok 4. Poinformuj RecyclerView o adapterze
Aby uzyskać uchwyty do wyświetlania, RecyclerView
musi znać adapter.
- Otwórz aplikację
SleepTrackerFragment.kt
. - W
onCreateview()
utwórz adapter. Umieść ten kod po utworzeniu modeluViewModel
i przed instrukcjąreturn
.
val adapter = SleepNightAdapter()
- Powiąż
adapter
zRecyclerView
.
binding.sleepList.adapter = adapter
- Wyczyść i odbuduj projekt, aby zaktualizować obiekt
binding
.
Jeśli nadal występują błędy związane zbinding.sleepList
lubbinding.FragmentSleepTrackerBinding
, unieważnij pamięć podręczną i uruchom ponownie urządzenie. (Wybierz Plik > Unieważnij pamięć podręczną / uruchom ponownie).
Jeśli uruchomisz aplikację teraz, nie wystąpią błędy, ale nie zobaczysz żadnych danych. Aby to zrobić, kliknij Rozpocznij, a następnie Zatrzymaj.
Krok 5. Pobierz dane do przejściówki
Na razie masz adapter i sposób przesyłania danych z adaptera do RecyclerView
. Teraz musisz pobrać dane do adaptera z urządzenia ViewModel
.
- Otwórz aplikację
SleepTrackerViewModel
. - Znajdź zmienną
nights
, która przechowuje wszystkie dostępne dane dotyczące snu. Zmiennanights
jest ustawiana przez wywołaniegetAllNights()
w bazie danych. - Usuń pole
private
z tabelinights
, ponieważ utworzysz obserwatora, który musi uzyskać dostęp do tej zmiennej. Deklaracja powinna wyglądać tak:
val nights = database.getAllNights()
- W pakiecie
database
otwórzSleepDatabaseDao
. - Znajdź funkcję
getAllNights()
. Pamiętaj, że ta funkcja zwraca listę wartościSleepNight
jakoLiveData
. Oznacza to, że zmiennanights
zawiera wartośćLiveData
, która jest aktualizowana przez parametrRoom
. Możesz ją obserwować, gdy wartośćnights
się zmieni. - Otwórz aplikację
SleepTrackerFragment
. - W
onCreateView()
poniżej koduadapter
utwórz obserwator na zmiennejnights
.
Dodając fragmentviewLifecycleOwner
jako właściciela cyklu życia, możesz zapewnić, że to obserwator będzie aktywny tylko wtedy, gdy na ekranie widoczny jestRecyclerView
.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
})
- Za każdym razem, gdy otrzymasz niepustą wartość (w przypadku klasy
nights
), przypisz ją do adapteradata
. To jest pełny kod dla obserwatora i ustawienia danych:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})
- Utwórz i uruchom kod.
Jeśli adapter działa, na liście znajdują się wartości dotyczące jakości snu. Zrzut ekranu po lewej stronie pokazuje -1 po kliknięciu Start. Zrzut ekranu po prawej stronie pokazuje zaktualizowany wynik jakości, gdy klikniesz Zatrzymaj i wybierzesz ocenę jakości.
Krok 6. Dowiedz się, w jaki sposób użytkownicy korzystają z ekranów
RecyclerView
Odświeża właścicieli widoków danych, co oznacza, że używa ich ponownie. RecyclerView
Te elementy widoku są odświeżane, więc upewnij się, że onBindViewHolder()
ustawia lub zresetuje wszelkie dostosowania, które poprzednie elementy mogły ustawiać na takim elemencie.
Możesz na przykład ustawić kolor tekstu na czerwony w widokach, w których ocena jakości jest niższa lub równa 1, co oznacza słabą jakość snu.
- W klasie
SleepNightAdapter
dodaj następujący kod na końcu polaonBindViewHolder()
.
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
}
- Uruchom aplikację.
- Dodaj dane dotyczące niskiej jakości snu, a ich liczba będzie czerwona.
- Dodaj wysokie oceny jakości snu, dopóki na ekranie nie pojawi się czerwona liczba.
GdyRecyclerView
będzie ponownie używać tych samych właścicieli, w celu uzyskania wysokiej jakości ocena wykorzysta też 1 z tych użytkowników. Najwyższa ocena jest błędnie wyświetlana na czerwono.
- Aby rozwiązać ten problem, dodaj instrukcję
else
, aby ustawić kolor na czarny, jeśli jakość nie jest niższa od tej wartości.
Jeśli oba te warunki są jednoznacznie określone, właściciel widoku użyje koloru odpowiedniego dla każdego elementu.
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
} else {
// reset
holder.textView.setTextColor(Color.BLACK) // black
}
- Uruchom aplikację, a liczby powinny zawsze mieć odpowiedni kolor.
Gratulacje! Teraz masz w pełni funkcjonalne podstawowe urządzenie RecyclerView
.
W tym zadaniu zastąpisz prosty widok, który może wyświetlać więcej danych w nocy.
Prosty element ViewHolder
dodany do Util.kt
powoduje zawijanie TextView
w TextItemViewHolder
.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
Dlaczego RecyclerView
nie używa bezpośrednio właściwości TextView
? Jeden wiersz kodu zapewnia wiele funkcji. ViewHolder
opisuje widok elementu i metadane miejsca w obrębie obiektu RecyclerView
. RecyclerView
korzysta z tej funkcji do prawidłowego pozycjonowania widoku podczas przewijania listy oraz do tworzenia interesujących rzeczy, np. animacji, gdy elementy są dodawane lub usuwane w Adapter
.
Jeśli RecyclerView
potrzebuje dostępu do widoków przechowywanych w narzędziu ViewHolder
, może to zrobić za pomocą właściwości itemView
właściciela widoku. Zmienna RecyclerView
używa itemView
, gdy wiąże element, który ma być wyświetlany na ekranie, rysuje dekoracje w widoku takim jak obramowanie, oraz implementuje ułatwienia dostępu.
Krok 1. Utwórz układ elementu
W tym kroku utworzysz plik układu 1 elementu. Układ składa się z: ConstraintLayout
z ImageView
dla jakości snu, TextView
dla długości snu oraz TextView
dla jakości w formie tekstu. Ponieważ układy zostały już wcześniej skonfigurowane, skopiuj i wklej podany kod XML.
- Utwórz nowy plik zasobu układu i nadaj mu nazwę
list_item_sleep_night
. - Zastąp cały kod w pliku kodem poniżej. Następnie zapoznaj się z nowo utworzonym układem.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/quality_image"
android:layout_width="@dimen/icon_size"
android:layout_height="60dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_sleep_5" />
<TextView
android:id="@+id/sleep_length"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/quality_image"
app:layout_constraintTop_toTopOf="@+id/quality_image"
tools:text="Wednesday" />
<TextView
android:id="@+id/quality_string"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="@+id/sleep_length"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/sleep_length"
app:layout_constraintTop_toBottomOf="@+id/sleep_length"
tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>
- Otwórz kartę Projekt w Android Studio. W widoku projektu Twój układ wygląda jak zrzut ekranu po lewej stronie. W widoku planu wygląda jak zrzut ekranu po prawej stronie.
Krok 2. Utwórz obiekt ViewHolder
- Otwórz aplikację
SleepNightAdapter.kt
. - Utwórz klasę wewnątrz
SleepNightAdapter
o nazwieViewHolder
i przedłuż ją oRecyclerView.ViewHolder
.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
- Więcej informacji o wyświetleniach znajdziesz w sekcji
ViewHolder
. Potrzebujesz odwołania do widoków, któreViewHolder
aktualizuje. Za każdym razem, gdy to zrobisz,ViewHolder
musisz uzyskać dostęp do obrazu i obu tych elementów. (Przekonwertujesz ten kod, by używać powiązania danych później).
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)
Krok 3. Użyj ViewHolder w SleepNightAdapter
- W definicji
SleepNightAdapter
zamiastTextItemViewHolder
użyj właśnie utworzonej wartościSleepNightAdapter.ViewHolder
.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {
Zaktualizuj onCreateViewHolder()
:
- Zmień podpis
onCreateViewHolder()
, aby zwracałViewHolder
. - Zmień inflator układu, aby użyć właściwego zasobu układu:
list_item_sleep_night
. - Usuń obsadę na:
TextView
. - Zamiast zwracać
TextItemViewHolder
, zwróćViewHolder
.
Oto zaktualizowana funkcjaonCreateViewHolder()
:
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater =
LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night,
parent, false)
return ViewHolder(view)
}
Zaktualizuj onBindViewHolder()
:
- Zmień podpis
onBindViewHolder()
, tak by parametrholder
był elementemViewHolder
, a nieTextItemViewHolder
. - W kodzie
onBindViewHolder()
usuń cały kod, z wyjątkiem definicjiitem
. - Określ
val
res
zawierający odniesienie doresources
dla tego widoku.
val res = holder.itemView.context.resources
- Ustaw czas trwania tekstu tekstu w widoku tekstowym
sleepLength
. Skopiuj poniższy kod, który wywołuje funkcję formatowania dostarczoną z kodem startowym.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
- Wystąpił błąd, ponieważ trzeba określić
convertDurationToFormatted()
. Otwórz plikUtil.kt
i usuń z komentarza kod oraz powiązane z nim importy. (Wybierz Kod > Skomentuj z wierszem Komentarze). - Za
onBindViewHolder()
użyj ustawieniaconvertNumericQualityToString()
, aby ustawić jakość.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
- Być może trzeba będzie zaimportować te funkcje ręcznie.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
- Ustaw odpowiednią ikonę jakości. Nowa ikona
ic_sleep_active
jest dostępna w kodzie początkowym.
holder.qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
- Oto ukończona funkcja
onBindViewHolder()
, która ustawia wszystkie dane dla obiektuViewHolder
:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
val res = holder.itemView.context.resources
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
holder.qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
- Uruchom aplikację. Ekran powinien wyglądać jak na zrzucie ekranu poniżej, wyświetlając ikonę jakości snu oraz tekst dotyczący czasu trwania i jakości snu.
Twoja transakcja RecyclerView
została zakończona! Wiesz już, jak stosować Adapter
i ViewHolder
. Połączone razem, aby wyświetlić listę z RecyclerView
Adapter
.
Twój kod do tej pory pokazuje proces tworzenia adaptera i uchwytu wyświetlania. Możesz jednak poprawić ten kod. Kod do wyświetlenia i kod do zarządzania właścicielami widoków są wymieszane, a onBindViewHolder()
zna szczegóły aktualizowania ViewHolder
.
W aplikacji w wersji produkcyjnej może być używanych kilku posiadaczy widoków danych, bardziej złożone adaptery czy zmiany wprowadzane przez wielu deweloperów. Musisz uporządkować kod w taki sposób, by wszystko, co było związane z właścicielem widoku, było widoczne tylko w tym widoku.
Krok 1. refaktoryzacja onBindViewHolder()
W tym kroku refaktoryzacja kodu i przeniesienie wszystkich funkcji widoku do ViewHolder
. Celem tego refaktoryzacji nie jest zmiana wyglądu aplikacji dla użytkownika, ale ułatwienie i usprawnienie pracy programistów przy tworzeniu kodu. Na szczęście Android Studio ma narzędzia, które Ci w tym pomogą.
- W polu
SleepNightAdapter
w poluonBindViewHolder()
wybierz wszystko oprócz instrukcji, aby zadeklarować zmiennąitem
. - Kliknij prawym przyciskiem myszy, a następnie wybierz Fact > Extract > Function.
- Nazwij funkcję
bind
i zaakceptuj sugerowane parametry. Kliknij OK.
Funkcjabind()
jest umieszczona poniżejonBindViewHolder()
.
private fun bind(holder: ViewHolder, item: SleepNight) {
val res = holder.itemView.context.resources
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
holder.quality.text = convertNumericQualityToString(item.sleepQuality, res)
holder.qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
- Umieść kursor na słowie
holder
parametruholder
o wartościbind()
. NaciśnijAlt+Enter
(Option+Enter
na Macu), by otworzyć menu intencji. Wybierz Przekształć parametr w odbiornika, aby przekonwertować go na funkcję rozszerzenia o takim podpisie:
private fun ViewHolder.bind(item: SleepNight) {...}
- Wytnij i wklej funkcję
bind()
w narzędziuViewHolder
. - Udostępnij
bind()
publicznie. - W razie potrzeby zaimportuj
bind()
do adaptera. - Ponieważ znajduje się on teraz w
ViewHolder
, możesz usunąć częśćViewHolder
podpisu. Oto ostateczny kod funkcjibind()
w klasieViewHolder
.
fun bind(item: SleepNight) {
val res = itemView.context.resources
sleepLength.text = convertDurationToFormatted(
item.startTimeMilli, item.endTimeMilli, res)
quality.text = convertNumericQualityToString(
item.sleepQuality, res)
qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
Krok 2: refaktoryzacja onCreateViewHolder
Metoda onCreateViewHolder()
w adapterze powoduje aktualnie odświeżenie widoku z zasobu układu ViewHolder
. Jednak inflacja nie ma nic wspólnego z adapterem, a tylko z ViewHolder
. Inflacja powinna się pojawić w ViewHolder
.
- W narzędziu
onCreateViewHolder()
zaznacz cały kod w treści. - Kliknij prawym przyciskiem myszy, a następnie wybierz Fact > Extract > Function.
- Nazwij funkcję
from
i zaakceptuj sugerowane parametry. Kliknij OK. - Umieść kursor na nazwie funkcji
from
. NaciśnijAlt+Enter
(Option+Enter
na Macu), by otworzyć menu intencji. - Wybierz Przenieś do obiektu towarzyszącego. Funkcja
from()
musi znajdować się w obiekcie towarzyszącym, aby można było ją wywołać w klasieViewHolder
, a nie w instancjiViewHolder
. - Przenieś obiekt
companion
do klasyViewHolder
. - Udostępnij
from()
publicznie. - W
onCreateViewHolder()
zmień instrukcjęreturn
, by zwracała wynik wywołaniafrom()
w klasieViewHolder
.
Ukończone metodyonCreateViewHolder()
ifrom()
powinny wyglądać tak jak poniżej, a Twój kod powinien być tworzony i uruchamiany bez błędów.
override fun onCreateViewHolder(parent: ViewGroup, viewType:
Int): ViewHolder {
return ViewHolder.from(parent)
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night, parent, false)
return ViewHolder(view)
}
}
- Zmień podpis klasy
ViewHolder
, tak aby konstruktor był prywatny. Ponieważfrom()
jest teraz metodą zwracającą nową instancjęViewHolder
, nikt nie musi już wywoływać konstruktora elementuViewHolder
.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
- Uruchom aplikację. Powinna być kompilowana i uruchamiana w taki sam sposób jak wcześniej, czyli po refaktoryzacji.
Projekt w Androidzie Studio: RecyclerViewFundamentals
- Wyświetlanie listy lub siatki danych to jedno z najczęstszych zadań Androida w interfejsie użytkownika.
RecyclerView
został zaprojektowany z myślą o wydajności, nawet jeśli wyświetlasz bardzo duże listy. RecyclerView
umożliwia tylko przetwarzanie i rysowanie elementów widocznych obecnie na ekranie.- Gdy element przewija się na ekranie, jest odświeżany. Oznacza to, że element jest wypełniony nowymi treściami przewijanymi na ekran.
- Wzorzec adaptacyjny w inżynierii oprogramowania ułatwia współpracę obiektu z innym interfejsem API.
RecyclerView
wykorzystuje adapter, aby przekształcić dane aplikacji w coś, co można wyświetlić, bez konieczności zmiany sposobu przechowywania i przetwarzania danych przez aplikację.
Aby wyświetlać dane w narzędziu RecyclerView
, potrzebujesz tych elementów:
- RecyclerView
– aby utworzyć instancjęRecyclerView
, zdefiniuj element<RecyclerView>
w pliku układu. - ManagerManager
RecyclerView
RecyclerView
służy do porządkowania układu elementów w elemencieRecyclerView
, na przykład do umieszczenia ich na siatce lub liście liniowej.
W pliku<RecyclerView>
w pliku układu ustaw atrybutapp:layoutManager
na menedżer układu (np.LinearLayoutManager
lubGridLayoutManager
).
Możesz też automatycznie skonfigurowaćLayoutManager
w elemencieRecyclerView
. Omówimy tę technikę w późniejszych ćwiczeniach z programowania. - Układ każdego elementu
Utwórz układ jednego elementu danych w pliku układu XML. - Adapter
Utwórz adapter, który przygotuje dane i sposób ich wyświetlania wViewHolder
. Powiąż adapter z urządzeniemRecyclerView
.
Po uruchomieniuRecyclerView
użyje adaptera, aby określić, jak chcesz wyświetlić dane na ekranie.
Adapter wymaga zaimplementowania tych metod:
–getItemCount()
, aby zwrócić liczbę elementów.
–onCreateViewHolder()
, aby zwrócićViewHolder
element z listy.
–onBindViewHolder()
, aby dostosować dane do widoków elementu na liście. - ViewHolder
ViewHolder
zawiera informacje o wyświetlaniu jednego elementu z układu elementu. - Metoda
onBindViewHolder()
w adapterze dostosowuje dane do widoków. Zawsze zastępujesz tę metodę. ZazwyczajonBindViewHolder()
zwiększa układ elementu i umieszcza dane w widokach układu. RecyclerView
nie wie nic o danych, więcAdapter
musi informowaćRecyclerView
, gdy dane się zmienią. UżyjnotifyDataSetChanged()
, by powiadomićAdapter
, że dane zostały zmienione.
Kurs Udacity:
Dokumentacja dla programistów Androida:
Ta sekcja zawiera listę możliwych zadań domowych dla uczniów, którzy pracują w ramach tego ćwiczenia w ramach kursu prowadzonego przez nauczyciela. To nauczyciel może wykonać te czynności:
- W razie potrzeby przypisz zadanie domowe.
- Poinformuj uczniów, jak przesyłać zadania domowe.
- Oceń projekty domowe.
Nauczyciele mogą wykorzystać te sugestie tak długo, jak chcą lub chcą, i mogą przypisać dowolne zadanie domowe.
Jeśli samodzielnie wykonujesz te ćwiczenia z programowania, możesz sprawdzić swoją wiedzę w tych zadaniach domowych.
Odpowiedz na te pytania
Pytanie 1
Jak RecyclerView
wyświetla elementy? Wybierz wszystkie pasujące odpowiedzi.
▢ wyświetla listę na siatce lub liście.
▢ przewijanie w pionie lub poziomie.
▢ Przewijanie ukośne na większych urządzeniach, takich jak tablety.
▢ zezwala na układy niestandardowe, gdy lista lub siatka nie wystarczają do użytku.
Pytanie 2
Jakie są zalety korzystania z RecyclerView
? Wybierz wszystkie pasujące odpowiedzi.
▢ efektywnie wyświetlają duże listy.
▢ Automatycznie aktualizuje dane.
▢ minimalizuje odświeżanie, gdy element jest aktualizowany, usuwany lub dodawany do listy.
▢ wykorzystuje ponownie widok przewijany poza ekranem, by wyświetlić kolejny element przewijany na ekranie.
Pytanie 3
Jakie są przyczyny użycia adapterów? Wybierz wszystkie pasujące odpowiedzi.
▢ rozdzielenie wątpliwości ułatwia zmianę i testowanie kodu.
▢ RecyclerView
nie ma wpływu na wyświetlane dane.
▢ Warstwy przetwarzania danych nie muszą się martwić o sposób wyświetlania danych.
▢ Aplikacja będzie działać szybciej.
Pytanie 4
Które z poniższych stwierdzeń na temat firmy ViewHolder
są prawdziwe? Wybierz wszystkie pasujące odpowiedzi.
▢ Układ ViewHolder
jest zdefiniowany w plikach układu XML.
▢ Dla każdego zbioru danych w zbiorze danych jest dostępny ViewHolder
.
▢ RecyclerView
może zawierać więcej niż 1 element ViewHolder
.
▢ Adapter
wiąże dane z elementem ViewHolder
.
Rozpocznij następną lekcję: