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 poprzedniego ćwiczenia z programowania dowiesz się, jak pobierać dane z usługi internetowej i analizować odpowiedź do obiektu danych. Dzięki nim dowiesz się, jak wczytywać i wyświetlać zdjęcia z internetowego adresu URL. Wiesz też, jak utworzyć RecyclerView
i używać go do wyświetlania siatki obrazów na stronie przeglądu.
Co warto wiedzieć
- Jak tworzyć fragmenty i ich używać.
- Jak używać komponentów architektury, takich jak modele widoku danych, fabryki modeli, przekształcenia i
LiveData
. - Jak pobrać JSON z usługi internetowej REST i przeanalizować te dane w obiektach Kotlin za pomocą bibliotek Retrofit i Moshi.
- Jak utworzyć układ siatki z użyciem obiektu
RecyclerView
. - Jak działają
Adapter
,ViewHolder
iDiffUtil
.
Czego się nauczysz:
- Korzystanie z biblioteki Glide do ładowania i wyświetlania obrazów z internetowego adresu URL.
- Dowiedz się, jak używać siatki
RecyclerView
i adaptera siatki, aby wyświetlać siatkę obrazów. - Jak radzić sobie z potencjalnymi błędami podczas pobierania i wyświetlania obrazów.
Co chcesz zrobić
- Zmodyfikuj aplikację MarsRealEstate, aby uzyskać adres URL obrazu z danych właściwości Marsa, a następnie użyj Glide, aby wczytać i wyświetlić ten obraz.
- Dodaj do aplikacji animację wczytywania i ikonę błędu.
- Aby wyświetlić siatkę Marsa, użyj wartości
RecyclerView
. - Dodaj opis i obsługę błędów w elemencie
RecyclerView
.
W tym ćwiczeniu z programowania (i w powiązanych z nimi ćwiczeniach) będziesz używać aplikacji MarsRealEstate, która wyświetla usługi na Marsie. Aplikacja łączy się z serwerem internetowym, aby pobierać i wyświetlać dane o nieruchomościach, m.in. o ich cenie oraz o tym, czy nieruchomości jest na sprzedaż lub do wynajęcia. Zdjęcia przedstawiające każdą nieruchomość to rzeczywiste zdjęcia Marsa zrobione przez marsjańskie łaziki.
Wersja aplikacji utworzona w tym ćwiczeniu z programowania wypełnia stronę Przegląd, na której wyświetla się siatka obrazów. Obrazy to część danych usługi, które aplikacja otrzymuje z usługi internetowej Marsa. Twoja aplikacja będzie używać biblioteki Glide do wczytywania i wyświetlania obrazów, a RecyclerView
– do tworzenia układu siatki dla obrazów. Aplikacja będzie też płynnie obsługiwać błędy sieci.
Wyświetlenie zdjęcia z internetowego adresu może wydawać się proste, ale żeby to zadziałało, trzeba trochę napracować odpowiednie mechanizmy. Obraz musi zostać pobrany, zbuforowany i zdekodowany z formatu skompresowanego do użycia przez Androida. Obraz powinien znajdować się w pamięci podręcznej w pamięci podręcznej lub w obu tych miejscach. Trzeba to robić w wątkach w tle o niskim priorytecie, aby interfejs działał elastycznie. Aby uzyskać lepszą wydajność sieci i CPU, możesz pobrać i odkodować więcej niż jeden obraz naraz. Nauczenie się efektywnego ładowania obrazów z sieci może wymagać jedynie ćwiczeń z programowania.
Na szczęście możesz wykorzystać przygotowaną przez społeczność bibliotekę Glide do pobierania, buforowania, dekodowania i buforowania obrazów. Glide pozostawia dużo mniej pracy niż w przypadku wszystkiego od zera.
Lot wymaga dwóch rzeczy:
- Adres URL obrazu, który chcesz wczytać i wyświetlić.
- Obiekt
ImageView
do wyświetlania tego obrazu.
W tym zadaniu dowiesz się, jak używać Glide do wyświetlania pojedynczego obrazu z usługi internetowej nieruchomości. Na liście właściwości zwracanych przez usługę internetową jest wyświetlany obraz reprezentujący pierwszą właściwość Marsa. Oto zrzuty ekranu przed i po:
Krok 1. Dodaj zależność Glide
- Otwórz aplikację MarsRealEstate z ostatniego ćwiczenia z programowania. (Jeśli nie masz aplikacji, możesz pobrać aplikację MarsRealEstateNetwork tutaj).
- Uruchom aplikację, aby sprawdzić, jak działa. Wyświetla szczegółowe informacje o nieruchomości, która jest hipotetycznie dostępna na Marsie.
- Otwórz build.gradle (Moduł: aplikacja).
- W sekcji
dependencies
dodaj ten wiersz na potrzeby biblioteki Glide:
implementation "com.github.bumptech.glide:glide:$version_glide"
Zauważ, że numer wersji jest już zdefiniowany oddzielnie w pliku Gradle projektu.
- Kliknij Sync Now (Synchronizuj teraz), by ponownie utworzyć projekt z nową zależnością.
Krok 2. Zaktualizuj model widoku
Następnie zaktualizuj klasę OverviewViewModel
, aby uwzględnić dane na żywo o jednej usłudze na Marsie.
- Otwórz aplikację
overview/OverviewViewModel.kt
. Tuż pod polemLiveData
obiektu_response
dodaj wewnętrzne (zmienne) i zewnętrzne (stałe) dane na żywo dla pojedynczego obiektuMarsProperty
.
Jeśli chcesz, możesz zaimportować klasęMarsProperty
(com.example.android.marsrealestate.network.MarsProperty
).
private val _property = MutableLiveData<MarsProperty>()
val property: LiveData<MarsProperty>
get() = _property
- W metodzie
getMarsRealEstateProperties()
znajdź wiersz w blokutry/catch {}
, który ustawia_response.value
na liczbę właściwości. Dodaj test pokazany poniżej. Jeśli dostępne są obiektyMarsProperty
, ten test ustawia wartość_property
LiveData
na pierwszą właściwość wlistResult
.
if (listResult.size > 0) {
_property.value = listResult[0]
}
Cały blok try/catch {}
wygląda teraz tak:
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
if (listResult.size > 0) {
_property.value = listResult[0]
}
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
- Otwórz plik
res/layout/fragment_overview.xml
. W elemencie<TextView>
zmieńandroid:text
, aby powiązać z komponentemimgSrcUrl
property
LiveData
:
android:text="@{viewModel.property.imgSrcUrl}"
- Uruchom aplikację.
TextView
wyświetla tylko adres URL obrazu w pierwszej właściwości Marsa. Do tej pory udało Ci się skonfigurować model widoku i dane na żywo dla tego adresu URL.
Krok 3. Utwórz adapter wiązania i wywołaj wywołanie Glide
Teraz masz już URL do wyświetlenia obrazu. Teraz możesz zacząć go używać z gestem Glide. W tym kroku używasz adaptera, aby pobrać adres URL z atrybutu XML powiązanego z elementem ImageView
, i użyj narzędzia Glide, aby wczytać obraz. Adaptery wiążące to metody rozszerzeń, które znajdują się między widokiem danych a powiązanymi danymi, aby zapewnić niestandardowe działanie w przypadku zmiany danych. W takim przypadku niestandardowym zachowaniem jest wywołanie Glide w celu wczytania obrazu z adresu URL w tagu ImageView
.
- Otwórz aplikację
BindingAdapters.kt
. Ten plik będzie zawierać adaptery wiążące, których używasz w całej aplikacji. - Utwórz funkcję
bindImage()
, która przyjmuje parametryImageView
iString
jako parametry. Dodaj adnotację do funkcji@BindingAdapter
. Adnotacja@BindingAdapter
informuje o wiązaniu danych, że ten adapter wiązania ma zostać użyty, gdy element XML ma atrybutimageUrl
.
Importujandroidx.databinding.BindingAdapter
iandroid.widget.ImageView
na żądanie.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
}
- W funkcji
bindImage()
dodaj bloklet {}
dla argumentuimgUrl
:
imgUrl?.let {
}
- W bloku
let {}
dodaj poniższy wiersz, aby przekonwertować ciąg adresu URL (z pliku XML) na obiektUri
. W razie potrzeby zaimportujandroidx.core.net.toUri
.
Ostateczny obiektUri
powinien używać schematu HTTPS, ponieważ serwer, z którego pobierasz obrazy, wymaga tego schematu. Aby użyć schematu HTTPS, dodajbuildUpon.scheme("https")
do konstruktoratoUri
. MetodatoUri()
jest funkcją rozszerzenia Kotlin z biblioteki podstawowej KTX Androida, więc wygląda na to, że jest częścią klasyString
.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
- Pozostaw wewnątrz
let {}
, wywołajGlide.with()
, aby wczytać obraz z obiektuUri
do elementuImageView
. Importujcom.bumptech.glide.Glide
na żądanie.
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)
Krok 4. Zaktualizuj układ i fragmenty
Chociaż Glide wczytała obraz, to jeszcze nic nie jest widoczne. Następnym krokiem jest zaktualizowanie układu i fragmentów za pomocą tagu ImageView
, aby wyświetlać obraz.
- Otwórz aplikację
res/layout/gridview_item.xml
. To jest plik zasobu dotyczący układu, który zostanie wykorzystany dla każdego elementuRecyclerView
w dalszej części ćwiczenia. Tymczasowo używasz go tylko do wyświetlania pojedynczego obrazu. - Nad elementem
<ImageView>
dodaj element<data>
do wiązania danych i powiąż go z klasąOverviewViewModel
:
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
- Dodaj atrybut
app:imageUrl
do elementuImageView
, aby użyć nowego adaptera wiązania obrazów:
app:imageUrl="@{viewModel.property.imgSrcUrl}"
- Otwórz aplikację
overview/OverviewFragment.kt
. W metodzieonCreateView()
skomentuj komentarz, który uzupełnia klasęFragmentOverviewBinding
i przypiszesz ją do zmiennej wiązania. Jest to tylko tymczasowa konfiguracja. Wróć tu później.
//val binding = FragmentOverviewBinding.inflate(inflater)
- Dodaj wiersz, aby zastąpić klasę
GridViewItemBinding
. Importujcom.example.android.marsrealestate. databinding.GridViewItemBinding
, gdy pojawi się taka prośba.
val binding = GridViewItemBinding.inflate(inflater)
- Uruchom aplikację. Zobaczysz na liście wyników zdjęcie z pierwszego
MarsProperty
.
Krok 5. Dodaj proste obrazy wczytywania i błędów
Dzięki przesuwaniu można zwiększyć komfort użytkowania. Wyświetla obraz zastępczy podczas ładowania obrazu i wyświetla błąd, jeśli nie uda się go załadować (na przykład jeśli nie ma obrazu lub jest on uszkodzony). W tym kroku dodasz tę funkcję do adaptera wiązania i do układu.
- Otwórz aplikację
res/drawable/ic_broken_image.xml
i kliknij kartę Podgląd po prawej stronie. W przypadku zdjęcia błędu używasz ikony uszkodzonego obrazu, która jest dostępna we wbudowanej bibliotece ikon. Ten wektorowy obiekt rysowalny jest za pomocą atrybutuandroid:tint
, aby kolorować ikonę w kolorze szarym.
- Otwórz aplikację
res/drawable/loading_animation.xml
. Jest to animacja, która jest definiowana za pomocą tagu<animate-rotate>
. Animacja obraca obiekt rysowany (loading_img.xml
) wokół punktu środkowego. (Animacja nie jest widoczna na podglądzie).
- Wróć do pliku
BindingAdapters.kt
. W metodziebindImage()
zmień wywołanieGlide.with()
na wywołanie funkcjiapply()
międzyload()
ainto()
. W razie potrzeby zaimportujcom.bumptech.glide.request.RequestOptions
.
Ten kod ustawia obraz zastępczy, który ma być używany podczas wczytywania (elementloading_animation
, który można rysować). Kod ustawia też obraz do użycia w przypadku niepowodzenia ładowania obrazu (rysunekbroken_image
). Pełna metodabindImage()
wygląda teraz tak:
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri =
imgUrl.toUri().buildUpon().scheme("https").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(RequestOptions()
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image))
.into(imgView)
}
}
- Uruchom aplikację. W zależności od szybkości połączenia sieciowego przez chwilę może się pojawić obraz wczytywania podczas pobierania i wyświetlania gestu usługi. Ikona uszkodzonego obrazu nie będzie jeszcze widoczna, nawet jeśli wyłączysz sieć – naprawisz ją w ostatniej części ćwiczeń z programowania.
Twoja aplikacja wczytuje teraz informacje o usłudze z internetu. Wykorzystując dane z pierwszego elementu listy MarsProperty
, udało Ci się utworzyć w widoku danych właściwość LiveData
i używasz adresu URL obrazu z tych danych do wypełniania elementu ImageView
. Jednak aplikacja ma wyświetlać siatkę obrazów, więc chcesz użyć tagu RecyclerView
z elementem GridLayoutManager
.
Krok 1. Zaktualizuj model widoku
Obecnie model widoku danych zawiera obiekt _property
LiveData
, który zawiera 1 obiekt MarsProperty
– pierwszy z listy odpowiedzi usługi internetowej. W tym kroku zmienisz wartość LiveData
tak, by przechowywała całą listę obiektów MarsProperty
.
- Otwórz aplikację
overview/OverviewViewModel.kt
. - Zmień prywatną zmienną
_property
na_properties
. Zmień typ na listę obiektówMarsProperty
.
private val _properties = MutableLiveData<List<MarsProperty>>()
- Zamień zewnętrzne dane transmisji na żywo z
property
naproperties
. Dodaj listę także do typuLiveData
:
val properties: LiveData<List<MarsProperty>>
get() = _properties
- Przewiń w dół do metody
getMarsRealEstateProperties()
. W blokutry {}
zastąp cały test dodany w poprzednim zadaniu wierszem widocznym poniżej. ZmiennalistResult
zawiera listę obiektówMarsProperty
, więc możesz ją tylko przypisać do kategorii_properties.value
, zamiast testować prawidłową odpowiedź.
_properties.value = listResult
Cały blok try/catch
wygląda teraz tak:
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
_properties.value = listResult
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
Krok 2. Zaktualizuj układ i fragmenty
Następnym krokiem jest zmiana układu i fragmentów aplikacji tak, aby korzystały z widoku odświeżającego i układu siatki, a nie z widoku pojedynczego obrazu.
- Otwórz aplikację
res/layout/gridview_item.xml
. Zmień wiązanie danych zOverviewViewModel
naMarsProperty
i zmień nazwę zmiennej na"property"
.
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
- W elemencie
<ImageView>
zmień atrybutapp:imageUrl
, aby odnosił się do adresu URL obrazu w obiekcieMarsProperty
:
app:imageUrl="@{property.imgSrcUrl}"
- Otwórz aplikację
overview/OverviewFragment.kt
. WonCreateview()
usuń komentarz do wiersza, który zwiększa wartośćFragmentOverviewBinding
. Usuń lub skomentuj linię, która powodujeGridViewBinding
. Te zmiany cofają tymczasowe zmiany wprowadzone w ostatnim zadaniu.
val binding = FragmentOverviewBinding.inflate(inflater)
// val binding = GridViewItemBinding.inflate(inflater)
- Otwórz aplikację
res/layout/fragment_overview.xml
. Usuń cały element<TextView>
. - Dodaj element
<RecyclerView>
, który korzysta z układuGridLayoutManager
igrid_view_item
na potrzeby jednego elementu:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="6dp"
android:clipToPadding="false"
app:layoutManager=
"androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />
Krok 3. Dodaj adapter siatki zdjęć
Teraz układ fragment_overview
ma RecyclerView
, a układ grid_view_item
ma pojedynczy ImageView
. W tym kroku powiążesz dane z elementem RecyclerView
za pomocą adaptera RecyclerView
.
- Otwórz aplikację
overview/PhotoGridAdapter.kt
. - Utwórz klasę
PhotoGridAdapter
z parametrami konstruktora widocznymi poniżej. KlasaPhotoGridAdapter
rozszerzaListAdapter
, którego konstruktor wymaga elementu listy, właściciela widoku i implementacjiDiffUtil.ItemCallback
.
Jeśli chcesz, możesz importować klasyandroidx.recyclerview.widget.ListAdapter
icom.example.android.marsrealestate.network.MarsProperty
. W kolejnych krokach wdrożysz pozostałe brakujące elementy tego konstruktora, które generują błędy.
class PhotoGridAdapter : ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
}
- Kliknij dowolne miejsce w klasie
PhotoGridAdapter
i naciśnijControl+i
, by zaimplementować metodyListAdapter
, czylionCreateViewHolder()
ionBindViewHolder()
.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
TODO("not implemented")
}
override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
TODO("not implemented")
}
- Na końcu definicji klasy
PhotoGridAdapter
dodaj nowo dodaną definicję obiektu towarzyszącego dlaDiffCallback
(jak pokazano poniżej).
Zaimportujandroidx.recyclerview.widget.DiffUtil
na żądanie.
ObiektDiffCallback
rozszerza właściwośćDiffUtil.ItemCallback
o typ obiektu, który chcesz porównać –MarsProperty
.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
- Naciśnij
Control+i
, by zaimplementować metody porównawcze dla tego obiektu:areItemsTheSame()
iareContentsTheSame()
.
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented")
}
override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented") }
- W przypadku metody
areItemsTheSame()
usuń zadanie do wykonania. Używaj operatora referencji Kotlin równości (===
), która zwracatrue
, jeśli obiekty odnoszą się dooldItem
inewItem
.
override fun areItemsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem === newItem
}
- W przypadku
areContentsTheSame()
użyj standardowego operatora równości tylko w identyfikatoracholdItem
inewItem
.
override fun areContentsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}
- Pozostając w klasie
PhotoGridAdapter
pod obiektem towarzyszącym, dodaj definicję klasy wewnętrznej dlaMarsPropertyViewHolder
, która powoduje rozszerzenieRecyclerView.ViewHolder
.
Importuj żądaniaandroidx.recyclerview.widget.RecyclerView
icom.example.android.marsrealestate.databinding.GridViewItemBinding
na żądanie.
Potrzebujesz elementuGridViewItemBinding
, by powiązaćMarsProperty
z układem, więc przekaż zmienną doMarsPropertyViewHolder
. Ponieważ podstawowa klasaViewHolder
wymaga widoku w konstruktorze, przekazujesz do niego wiążący widok główny.
class MarsPropertyViewHolder(private var binding:
GridViewItemBinding):
RecyclerView.ViewHolder(binding.root) {
}
- W
MarsPropertyViewHolder
utwórz metodębind()
, która przyjmuje obiektMarsProperty
jako argument i ustawia dla niegobinding.property
. WywołajexecutePendingBindings()
po ustawieniu właściwości, co spowoduje natychmiastowe uruchomienie aktualizacji.
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
}
- W usłudze
onCreateViewHolder()
usuń TODO i dodaj wiersz widoczny poniżej. Importujandroid.view.LayoutInflater
, gdy pojawi się taka prośba.
MetodaonCreateViewHolder()
musi zwracać nowy elementMarsPropertyViewHolder
, utworzony za pomocą parametruGridViewItemBinding
, który jest utworzony z elementu nadrzędnegoViewGroup
.
return MarsPropertyViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))
- W metodzie
onBindViewHolder()
usuń TODO i dodaj wiersze widoczne poniżej. Wywołujesz obiektgetItem()
, by pobrać obiektMarsProperty
powiązany z pozycjąRecyclerView
, a następnie przekażesz tę właściwość do metodybind()
wMarsPropertyViewHolder
.
val marsProperty = getItem(position)
holder.bind(marsProperty)
Krok 4. Dodaj przejściówkę i połącz części
Następnie użyj BindingAdapter
do zainicjowania PhotoGridAdapter
z listy obiektów MarsProperty
. Użycie danych BindingAdapter
do ustawienia danych typu RecyclerView
powoduje, że wiązanie danych automatycznie śledzi LiveData
na liście obiektów MarsProperty
. Następnie adapter wiązania zostanie automatycznie wywołany po zmianie listy MarsProperty
.
- Otwórz aplikację
BindingAdapters.kt
. - Na końcu pliku dodaj metodę
bindRecyclerView()
, która przyjmuje argumentyRecyclerView
i listę obiektówMarsProperty
. Dodaj do tej metody adnotację@BindingAdapter
.
W razie potrzeby zaimportujandroidx.recyclerview.widget.RecyclerView
icom.example.android.marsrealestate.network.MarsProperty
.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsProperty>?) {
}
- W ramach funkcji
bindRecyclerView()
prześlijrecyclerView.adapter
doPhotoGridAdapter
i wywołajadapter.submitList()
z danymi. Dzięki temuRecyclerView
będzie wiedzieć, kiedy dostępna jest nowa lista.
Importuj com.example.android.marsrealestate.overview.PhotoGridAdapter
, gdy pojawi się taka prośba.
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
- Otwórz aplikację
res/layout/fragment_overview.xml
. Dodaj atrybutapp:listData
do elementuRecyclerView
i ustaw go na wartośćviewmodel.properties
za pomocą wiązania danych.
app:listData="@{viewModel.properties}"
- Otwórz aplikację
overview/OverviewFragment.kt
. WonCreateView()
, tuż przed wywołaniemsetHasOptionsMenu()
, zainicjuj adapterRecyclerView
w tagubinding.photosGrid
dla nowego obiektuPhotoGridAdapter
.
binding.photosGrid.adapter = PhotoGridAdapter()
- Uruchom aplikację. Powinna być widoczna siatka
MarsProperty
obrazów. Aplikacja wyświetla ikonę postępu ładowania, gdy przewijasz stronę, by zobaczyć nowe obrazy. Jeśli włączysz tryb samolotowy, obrazy, które nie zostały jeszcze wczytane, będą wyświetlane jako ikony uszkodzonego obrazu.
Aplikacja MarsRealEstate wyświetla ikonę uszkodzonego obrazu, gdy nie można pobrać obrazu. Jednak w przypadku braku sieci aplikacja wyświetla pusty ekran.
To nie spodoba się użytkownikom. W tym zadaniu dodasz podstawową obsługę błędów, by użytkownik wiedział, co się dzieje. Jeśli internet nie jest dostępny, aplikacja wyświetli ikonę błędu połączenia. Podczas pobierania listy MarsProperty
aplikacja będzie wyświetlać animację wczytywania.
Krok 1. Dodaj stan do modelu widoku danych
Zacznij od utworzenia w widoku danych obiektu LiveData
reprezentującego stan żądania internetowego. Dostępne są 3 stany: wczytywanie, powodzenie i niepowodzenie. Stan wczytywania ma miejsce, kiedy czekasz na dane z wywołania kierowanego do źródła await()
.
- Otwórz aplikację
overview/OverviewViewModel.kt
. W górnej części pliku (po zaimportowaniu, przed definicją klasy) dodajenum
reprezentujący wszystkie dostępne stany:
enum class MarsApiStatus { LOADING, ERROR, DONE }
- Zmień nazwy zarówno wewnętrznych, jak i zewnętrznych definicji
_response
w całej klasieOverviewViewModel
na_status
. Ponieważ dodano obsługę_properties
LiveData
wcześniej w tym ćwiczeniu, cała odpowiedź dotycząca usługi internetowej nie została wykorzystana. Aby śledzić bieżący stan usługi, potrzebujeszLiveData
. Wystarczy, że zmienisz nazwę istniejących zmiennych.
Zmień też typy z String
na MarsApiStatus.
private val _status = MutableLiveData<MarsApiStatus>()
val status: LiveData<MarsApiStatus>
get() = _status
- Przewiń w dół do metody
getMarsRealEstateProperties()
i zmień także_response
na_status
. Zmień ciąg znaków"Success"
na stanMarsApiStatus.DONE
, a ciąg"Failure"
naMarsApiStatus.ERROR
. - Dodaj stan
MarsApiStatus.LOADING
u góry bloku adresutry {}
, przed wywołaniemawait()
. To początkowy stan, gdy jest uruchomiona aplikacja i oczekujesz na dane. Cały bloktry/catch {}
wygląda teraz tak:
try {
_status.value = MarsApiStatus.LOADING
var listResult = getPropertiesDeferred.await()
_status.value = MarsApiStatus.DONE
_properties.value = listResult
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
}
- Po stanie błędu w bloku
catch {}
ustaw_properties
LiveData
na pustą listę. Zostaną usunięteRecyclerView
.
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}
Krok 2. Dodaj adapter wiązania dla stanu ImageView
Teraz masz w modelu widoku stan, ale jest to po prostu zbiór stanów. Jak sprawić, by pojawiała się ona w samej aplikacji? W tym kroku używasz ikony ImageView
połączonej z wiązaniem danych, aby wyświetlać ikony stanu wczytywania i błędów. Gdy aplikacja jest w fazie wczytywania lub stanu błędu, ikona ImageView
powinna być widoczna. Po zakończeniu wczytywania ImageView
powinien być niewidoczny.
- Otwórz aplikację
BindingAdapters.kt
. Dodaj nowy adapter wiązania o nazwiebindStatus()
, który przyjmuje wartościImageView
iMarsApiStatus
jako argumenty. Importujcom.example.android.marsrealestate.overview.MarsApiStatus
, gdy pojawi się taka prośba.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}
- Aby przełączać się między stanami, dodaj parametr
when {}
w metodziebindStatus()
.
when (status) {
}
- W obrębie znaczników
when {}
dodaj zgłoszenie dotyczące stanu wczytywania (MarsApiStatus.LOADING
). W przypadku tego stanu ustawImageView
jako widoczny i przypisz mu animację wczytywania. Jest to ta sama animacja, która pojawiła się w Glide w poprzednim zadaniu. Importujandroid.view.View
, gdy pojawi się taka prośba.
when (status) {
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
}
}
- Dodaj zgłoszenie o stanie błędu, czyli
MarsApiStatus.ERROR
. Podobnie jak w przypadku stanuLOADING
ustaw stanImageView
na widoczny i ponownie użyj rysowanego błędu połączenia.
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}
- Dodaj zgłoszenie o stanie „Gotowe”:
MarsApiStatus.DONE
. Tutaj wyświetla się odpowiednia odpowiedź. Wyłącz widoczność stanuImageView
, by go ukryć.
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}
Krok 3. Dodaj stan ImageView do układu
- Otwórz aplikację
res/layout/fragment_overview.xml
. Poniżej elementuRecyclerView
w obiekcieConstraintLayout
dodajImageView
pokazany poniżej.
TenImageView
ma te same ograniczenia coRecyclerView
. Szerokość i wysokość są jednak mierzonewrap_content
, aby wyśrodkować obraz, a nie rozciągnąć go, aby wypełnić cały widok. Zwróć uwagę na atrybutapp:marsApiStatus
, który zawiera widok wywołującyBindingAdapter
w przypadku zmiany właściwości stanu w modelu widoku.
<ImageView
android:id="@+id/status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:marsApiStatus="@{viewModel.status}" />
- Włącz tryb samolotowy w emulatorze lub urządzeniu, aby zasymulować brak połączenia sieciowego. Skompiluj i uruchom aplikację, a zobaczysz komunikat o błędzie:
- Kliknij przycisk Wstecz, by zamknąć aplikację i wyłączyć tryb samolotowy. Wróć do ekranu Ostatnie.
Projekt na Android Studio: MarsRealEstateGrid
- Aby uprościć proces zarządzania obrazami, użyj biblioteki Gide do pobrania, zbuforowania, dekodowania i buforowania obrazów w aplikacji.
- Aby wczytać obraz z internetu, Glide potrzebuje dwóch rzeczy: adresu URL obrazu oraz obiektu
ImageView
, w którym zostanie umieszczony obraz. Aby określić te opcje, użyj metodload()
iinto()
w aplikacji Glide. - Adaptery wiążące to metody rozszerzeń, które znajdują się między widokiem danych a powiązanymi z nim danymi. Adaptery wiążące zapewniają niestandardowe działanie w przypadku zmiany danych, na przykład wywołanie Glide w celu wczytania obrazu z adresu URL do elementu
ImageView
. - Adaptery wiązania to metody rozszerzeń z adnotacją
@BindingAdapter
. - Aby dodać opcje do żądania gestami, użyj metody
apply()
. Na przykład użyjapply()
zplaceholder()
, aby określić rysowanie rysujące, iapply()
zerror()
, aby wskazać błąd rysowalny. - Aby utworzyć siatkę obrazów, użyj
RecyclerView
zGridLayoutManager
. - Aby zaktualizować listę właściwości po zmianie, użyj adaptera wiązania między elementem
RecyclerView
i układem.
Kurs Udacity:
Dokumentacja dla programistów Androida:
- Omówienie modelu Model
- Omówienie danych na żywo
- Koordynacje, oficjalna dokumentacja
- Adaptery wiążące
Inne:
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
Jakiej metody gestu używasz, by wskazać właściwość ImageView
, która będzie zawierać wczytany obraz?
▢ into()
▢ with()
▢ imageview()
▢ apply()
Pytanie 2
Jak określić obraz zastępczy, który ma wyświetlać się podczas przesuwania?
▢ Skorzystaj z metody into()
z rysunkiem.
▢ Użyj metody RequestOptions()
i wywołaj metodę placeholder()
z rysunkiem.
▢ Przypisz właściwość Glide.placeholder
do rysunku.
▢ Użyj metody RequestOptions()
i wywołaj metodę loadingImage()
z rysunkiem.
Pytanie 3
Jak wskazujecie, że metoda jest adapterem wiążącym?
▢ Wywołaj metodę setBindingAdapter()
na LiveData
.
▢ Umieść metodę w pliku Kotlin o nazwie BindingAdapters.kt
.
▢ Użyj atrybutu android:adapter
w układzie XML.
▢ Dodaj metodę @BindingAdapter
.
Rozpocznij następną lekcję:
Linki do innych ćwiczeń z programowania w tym kursie znajdziesz na stronie docelowej z ćwiczeniami z podstaw Androida Kotlin.