인터넷에서 이미지를 로드하여 표시하기

소개

이전 Codelab에서는 웹 서비스에서 데이터를 가져와 응답을 Kotlin 객체로 파싱하는 방법을 배웠습니다. 이 지식을 기반으로 이 Codelab에서는 웹 URL에서 사진을 로드하고 표시합니다. 또한 RecyclerView를 빌드하고 이 뷰를 사용해 개요 페이지에 이미지 그리드를 표시하는 방법을 다시 확인합니다.

기본 요건

  • 프래그먼트를 만들고 사용하는 방법
  • Retrofit 라이브러리와 Moshi 라이브러리를 사용하여 REST 웹 서비스에서 JSON을 검색하고 이 데이터를 Kotlin 객체로 파싱하는 방법
  • RecyclerView로 그리드 레이아웃을 구성하는 방법
  • Adapter, ViewHolder, DiffUtil의 작동 방식

학습할 내용

  • Coil 라이브러리를 사용하여 웹 URL에서 이미지를 로드하고 표시하는 방법
  • RecyclerView 및 그리드 어댑터를 사용하여 이미지 그리드를 표시하는 방법
  • 이미지를 다운로드하고 표시할 때 발생할 수 있는 오류를 처리하는 방법

빌드할 항목

  • 화성 데이터에서 이미지 URL을 가져오도록 MarsPhotos 앱을 수정하고 Coil을 사용해 이 이미지를 로드하고 표시합니다.
  • 앱에 로드 애니메이션과 오류 아이콘을 추가합니다.
  • RecyclerView를 사용하여 화성 이미지의 그리드를 표시합니다.
  • RecyclerView에 상태 및 오류 처리를 추가합니다.

필요한 항목

  • 최신 버전의 Chrome과 같은 최신 웹브라우저가 설치된 컴퓨터
  • 컴퓨터에서 인터넷 액세스가 가능해야 함

이 Codelab에서는 이전 Codelab의 MarsPhotos 앱을 계속 사용하여 작업합니다. MarsPhotos 앱은 웹 서비스에 연결하여 Retrofit을 사용해 검색된 Kotlin 객체 수를 가져와 표시합니다. 이 Kotlin 객체에는 NASA의 화성 탐사 로봇이 화성 표면에서 촬영한 실제 사진의 URL이 포함되어 있습니다.

이 Codelab에서 빌드하는 버전의 앱은 화성 사진을 이미지 그리드로 보여주는 개요 페이지를 채웁니다. 이미지는 앱이 Mars 웹 서비스에서 검색한 데이터의 일부입니다. 앱은 Coil 라이브러리를 사용하여 이미지를 로드해 표시하고 RecyclerView를 사용하여 이미지의 그리드 레이아웃을 만듭니다. 또한, 앱은 네트워크 오류를 적절히 처리합니다.

1b33675b009bee15.png

웹 URL에서 사진을 표시하는 것은 간단해 보일 수도 있지만 제대로 작동하려면 엔지니어링이 상당히 필요합니다. 이미지를 다운로드하고, 내부적으로 저장하고, 압축 형식에서 Android가 사용할 수 있는 이미지로 디코딩해야 합니다. 이미지는 메모리 내 캐시나 저장소 기반 캐시 또는 두 캐시 모두에 캐시해야 합니다. UI가 응답성을 유지하기 위해 이 모든 작업은 우선순위가 낮은 백그라운드 스레드에서 이루어져야 합니다. 또한 최상의 네트워크 및 CPU 성능을 위해 둘 이상의 이미지를 한 번에 가져오고 디코딩하는 것이 좋습니다.

다행히 커뮤니티에서 개발한 Coil이라는 라이브러리를 사용하여 이미지를 다운로드하고 버퍼링 및 디코딩하고 캐시할 수 있습니다. Coil을 사용하지 않으면 해야 할 작업이 훨씬 더 많습니다.

Coil에는 기본적으로 다음 두 가지가 필요합니다.

  • 로드하고 표시할 이미지의 URL
  • 이미지를 실제로 표시하는 ImageView 객체

이 작업에서는 Coil을 사용하여 Mars 웹 서비스의 단일 이미지를 표시하는 방법을 알아봅니다. 웹 서비스에서 반환되는 사진 목록에 있는 첫 번째 화성 사진의 이미지를 표시합니다. 다음은 전과 후의 스크린샷입니다.

Coil 종속 항목 추가하기

  1. 이전 Codelab의 MarsPhotos 솔루션 앱을 엽니다.
  2. 앱을 실행하여 어떻게 되는지 확인합니다. (검색한 화성 사진의 총 개수가 표시됨)
  3. build.gradle (Module: app)을 엽니다.
  4. dependencies 섹션에서 다음과 같은 Coil 라이브러리 줄을 추가합니다.
    // Coil
    implementation "io.coil-kt:coil:1.1.1"

Coil 문서 페이지에서 최신 버전의 라이브러리를 확인하고 업데이트하세요.

  1. Coil 라이브러리는 mavenCentral() 저장소에서 호스팅되어 제공됩니다. build.gradle (Project: MarsPhotos)의 맨 위 repositories 블록에 mavenCentral()을 추가합니다.
repositories {
   google()
   jcenter()
   mavenCentral()
}
  1. Sync Now를 클릭하여 새 종속 항목으로 프로젝트를 다시 빌드합니다.

ViewModel 업데이트하기

이 단계에서는 LiveData 속성을 OverviewViewModel 클래스에 추가하여 수신된 Kotlin 객체인 MarsPhoto를 저장합니다.

  1. overview/OverviewViewModel.kt를 엽니다. _status 속성 선언 바로 아래에 단일 MarsPhoto 객체를 저장할 수 있는 MutableLiveData 유형의 새로운 변경 가능 속성 _photos를 추가합니다.
private val _photos = MutableLiveData<MarsPhoto>()

요청이 있는 경우 com.example.android.marsphotos.network.MarsPhoto를 가져옵니다.

  1. _photos 선언 바로 아래에 LiveData<MarsPhoto> 유형의 photos라는 공개 지원 필드를 추가합니다.
val photos: LiveData<MarsPhoto> = _photos
  1. getMarsPhotos() 메서드의 try{} 블록 내에서 웹 서비스에서 검색되는 데이터를 listResult.로 설정하는 줄을 찾습니다.
try {
   val listResult = MarsApi.retrofitService.getPhotos()
   ...
}
  1. 검색되는 첫 번째 화성 사진을 새 변수 _photos에 할당합니다. listResult_photos.value로 변경합니다. 색인 0에 첫 번째 사진 URL을 할당합니다. 이렇게 하면 오류가 발생하며, 이 오류는 나중에 해결합니다.
try {
   _photos.value = MarsApi.retrofitService.getPhotos()[0]
   ...
}
  1. 다음 줄에서 status.value를 다음과 같이 업데이트합니다. listResult 대신 새 속성의 데이터를 사용합니다. 사진 목록의 첫 번째 이미지 URL을 표시합니다.
try {
   ...
   _status.value = "   First Mars image URL : ${_photos.value!!.imgSrcUrl}"

}
  1. 이제 전체 try{} 블록은 다음과 같습니다.
try {
   _photos.value = MarsApi.retrofitService.getPhotos()[0]
   _status.value = "   First Mars image URL : ${_photos.value!!.imgSrcUrl}"
}
  1. 앱을 실행합니다. TextView가 이제 첫 번째 화성 사진의 URL을 표시합니다. 지금까지 ViewModel을 설정하고 이 URL의 LiveData를 설정했습니다.

b8ac93805b69b03a.png

결합 어댑터 사용하기

결합 어댑터는 뷰의 맞춤 속성을 위한 맞춤 setter를 만드는 데 사용되는 주석 처리된 메서드입니다.

일반적으로 XML에서 android:text="Sample Text" 코드를 사용하여 속성을 설정하는 경우 Android 시스템은 setText(String: text) 메서드를 통해 설정되는 text 속성과 같은 이름의 setter를 자동으로 찾습니다. setText(String: text) 메서드는 Android 프레임워크에서 제공하는 일부 뷰의 setter 메서드입니다. 결합 어댑터를 사용하여 유사한 동작을 맞춤설정할 수 있습니다. 데이터 결합 라이브러리에서 호출되는 맞춤 속성과 맞춤 로직을 제공할 수 있습니다.

예:

단순히 이미지 뷰에서 드로어블 이미지를 설정하는 setter를 호출하는 것보다 더 복잡한 무언가를 하려면 인터넷에서 UI 스레드(기본 스레드)의 로드 이미지를 가져오는 것을 고려합니다. 먼저 맞춤 속성을 선택하여 ImageView에 이미지를 할당합니다. 다음 예에서는 imageUrl입니다.

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:imageUrl="@{product.imageUrl}"/>

코드를 추가하지 않으면 시스템이 ImageView에서 setImageUrl(String) 메서드를 찾으며, 찾지 못해 오류가 발생합니다. 프레임워크에서 제공되지 않는 맞춤 속성이기 때문입니다. app:imageUrl 속성을 구현하여 ImageView로 설정하는 방법을 만들어야 합니다. 이렇게 하려면 결합 어댑터(주석 처리된 메서드)를 사용합니다.

결합 어댑터의 예:

@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
    imgUrl?.let {
        // Load the image in the background using Coil.
        }
    }
}

@BindingAdapter 주석은 속성 이름을 매개변수로 사용합니다.

bindImage 메서드에서 첫 번째 메서드 매개변수는 타겟 뷰의 유형이고 두 번째 매개변수는 속성에 설정되는 값입니다.

메서드 내부에서 Coil 라이브러리는 UI 스레드에서 이미지를 로드하여 ImageView로 설정합니다.

결합 어댑터 만들기 및 Coil 사용하기

  1. com.example.android.marsphotos 패키지에서 BindingAdapters라는 Kotlin 파일을 만듭니다. 이 파일은 앱 전반에 사용하는 결합 어댑터를 보유하게 됩니다.

a04afbd6ae8ccfcd.png

  1. BindingAdapters.kt에서 매개변수로 ImageViewString을 사용하는 bindImage() 함수를 만듭니다.
fun bindImage(imgView: ImageView, imgUrl: String?) {

}

요청이 있는 경우 android.widget.ImageView를 가져옵니다.

  1. 함수에 @BindingAdapter 주석을 추가합니다. @BindingAdapter 주석은 뷰 항목에 imageUrl 속성이 있는 경우 이 결합 어댑터를 실행하도록 데이터 결합에 지시합니다.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {

}

요청이 있는 경우 androidx.databinding.BindingAdapter를 가져옵니다.

let 범위 함수

let은 Kotlin의 범위 함수 중 하나로, 이 함수를 사용하여 객체의 컨텍스트 내에서 코드 블록을 실행할 수 있습니다. Kotlin에는 5가지 범위 함수가 있습니다. 자세히 알아보려면 문서를 참고하세요.

사용:

let은 호출 체인의 결과에서 함수 하나 이상을 호출하는 데 사용됩니다.

let 함수는 안전 호출 연산자( ?.)와 함께 객체에서 null 안전 연산을 실행하는 데 사용됩니다. 이 경우 let 코드 블록은 객체가 null이 아닌 경우에만 실행됩니다.

  1. bindImage() 함수 내부에서 안전 호출 연산자를 사용하여 let{} 블록을 imageURL 인수에 추가합니다.
imgUrl?.let {
}
  1. let{} 블록 내부에서 toUri() 메서드를 사용해 URL 문자열을 Uri 객체로 변환하도록 다음 줄을 추가합니다. HTTPS 스키마를 사용하려면 buildUpon.scheme("https")toUri 빌더에 추가합니다. build()를 호출하여 객체를 빌드합니다.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()

요청이 있는 경우 androidx.core.net.toUri를 가져옵니다.

  1. let{} 블록 내부에서 imgUri 선언 다음에 Coilload(){}를 사용하여 imgUri 객체에서 imgView로 이미지를 로드합니다.
imgView.load(imgUri) {
}

요청이 있는 경우 coil.load를 가져옵니다.

  1. 전체 메서드는 다음과 같습니다.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
    imgUrl?.let {
        val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
        imgView.load(imgUri)
    }
}

레이아웃 및 프래그먼트 업데이트하기

이전 섹션에서 Coil 이미지 라이브러리를 사용하여 이미지를 로드했습니다. 이미지를 화면에서 보기 위한 다음 단계는 ImageView를 새 속성으로 업데이트하여 단일 이미지를 표시하는 것입니다.

Codelab 후반에 res/layout/grid_view_item.xmlRecyclerView의 각 그리드 항목을 위한 레이아웃 리소스 파일로 사용합니다. 이 작업에서는 이 파일을 임시로 사용하여 이전 작업에서 검색한 이미지 URL을 사용하여 이미지를 표시합니다. 임시로 fragment_overview.xml 대신 이 레이아웃 파일을 사용하게 됩니다.

  1. res/layout/grid_view_item.xml을 엽니다.
  2. <ImageView> 요소 위에 데이터 결합의 <data> 요소를 추가하고 OverviewViewModel 클래스에 결합합니다.
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsphotos.overview.OverviewViewModel" />
</data>
  1. 새 이미지 로드 결합 어댑터를 사용하도록 ImageView 요소에 app:imageUrl 속성을 추가합니다. photos에는 MarsPhotos가 서버에서 검색한 목록이 포함된다는 점을 기억하세요. 첫 번째 항목 URL을 imageUrl 속성에 할당합니다.
    <ImageView
        android:id="@+id/mars_image"
        ...
        app:imageUrl="@{viewModel.photos.imgSrcUrl}"
        ... />
  1. overview/OverviewFragment.kt를 엽니다. onCreateView() 메서드에서 FragmentOverviewBinding 클래스를 확장하고 결합 변수에 할당하는 줄을 주석 처리합니다. 이 줄을 삭제한 것을 이유로 오류가 표시됩니다. 이 오류는 일시적이며 나중에 수정합니다.
//val binding = FragmentOverviewBinding.inflate(inflater)
  1. fragment_overview.xml. 대신 grid_view_item.xml을 사용합니다. 다음 줄을 추가하여 GridViewItemBinding 클래스를 대신 확장합니다.
val binding = GridViewItemBinding.inflate(inflater)

요청이 있는 경우 com.example.android.marsphotos. databinding.GridViewItemBinding을 가져옵니다.

  1. 앱을 실행합니다. 이제 단일 화성 이미지가 표시됩니다.

e59b6e849e63ae2b.png

로드 이미지와 오류 이미지 추가하기

Coil을 사용하면 이미지를 로드하는 동안 자리표시자 이미지를 표시하고 로드 실패 시(예: 이미지가 없거나 손상된 경우) 오류 이미지를 표시함으로써 사용자 경험을 개선할 수 있습니다. 이 단계에서는 이러한 기능을 결합 어댑터에 추가합니다.

  1. res/drawable/ic_broken_image.xml을 열고 오른쪽에서 Design 탭을 클릭합니다. 오류 이미지의 경우 내장된 아이콘 라이브러리에서 사용할 수 있는 손상 이미지 아이콘을 사용합니다. 이 벡터 드로어블은 android:tint 속성을 사용하여 아이콘 색상을 회색으로 지정합니다.

467c213c859e1904.png

  1. res/drawable/loading_animation.xml을 엽니다. 이 드로어블은 이미지 드로어블 loading_img.xml을 중심점을 축으로 회전시키는 애니메이션입니다. (이 애니메이션이 미리보기에 표시되지 않습니다.)

6c1f87d1c932c762.png

  1. BindingAdapters.kt 파일로 돌아갑니다. bindImage() 메서드에서 다음과 같이 imgView.load(imgUri) 호출을 업데이트하여 후행 람다를 추가합니다. 이 코드는 로드하는 동안 사용할 자리표시자 로드 이미지(loading_animation 드로어블)를 설정합니다. 또한 이 코드는 이미지를 로드하지 못한 경우 사용할 이미지(broken_image 드로어블)를 설정합니다.
imgView.load(imgUri) {
   placeholder(R.drawable.loading_animation)
   error(R.drawable.ic_broken_image)
}
  1. 이제 전체 bindImage() 메서드는 다음과 같습니다.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
    imgUrl?.let {
        val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
        imgView.load(imgUri) {
            placeholder(R.drawable.loading_animation)
            error(R.drawable.ic_broken_image)
        }
    }
}
  1. 앱을 실행합니다. 네트워크 연결 속도에 따라 Glide가 속성 이미지를 다운로드하고 표시할 때 로드 이미지가 잠시 표시될 수도 있습니다. 그러나 네트워크를 사용 중지해도 손상 이미지 아이콘은 아직 표시되지 않습니다. 이 부분은 Codelab의 마지막 작업에서 수정합니다.

80553d5e5c7641de.gif

  1. overview/OverviewFragment.kt에서 적용한 임시 변경사항을 되돌립니다. onCreateview() 메서드에서 FragmentOverviewBinding을 확장하는 줄의 주석 처리를 삭제합니다. GridViewIteMBinding을 확장하는 줄을 삭제하거나 주석 처리합니다.
val binding = FragmentOverviewBinding.inflate(inflater)
 // val binding = GridViewItemBinding.inflate(inflater)

이제 앱이 인터넷에서 화성 사진을 로드합니다. 첫 번째 MarsPhoto 목록 항목의 데이터를 사용하여 ViewModelLiveData 속성을 만들고 이 화성 사진 데이터의 이미지 URL을 사용하여 ImageView를 채웠습니다. 하지만 앱이 이미지 그리드를 표시하는 것이 목표이므로, 이 작업에서는 그리드 레이아웃 관리자와 함께 RecyclerView를 사용하여 이미지 그리드를 표시합니다.

뷰 모델 업데이트하기

이전 작업에서는 OverviewViewModel에서 웹 서비스의 응답 목록에 있는 첫 번째 객체인 MarsPhoto 객체 하나를 보유하는 LiveData 객체 _photos를 추가했습니다. 이 단계에서는 MarsPhoto 객체의 전체 목록을 보유하도록 이 LiveData를 변경합니다.

  1. overview/OverviewViewModel.kt를 엽니다.
  2. _photos 유형을 MarsPhoto 객체 목록으로 변경합니다.
private val _photos = MutableLiveData<List<MarsPhoto>>()
  1. 또한 지원 속성 photos 유형을 List<MarsPhoto> 유형으로 바꿉니다.
 val photos: LiveData<List<MarsPhoto>> = _photos
  1. getMarsPhotos() 메서드 내에서 아래로 스크롤하여 try {} 블록을 찾습니다. MarsApi.retrofitService.getPhotos()

MarsPhoto 객체의 목록을 반환하며, 이 목록을 _photos.value에 할당하면 됩니다.

_photos.value = MarsApi.retrofitService.getPhotos()
_status.value = "Success: Mars properties retrieved"
  1. 이제 전체 try/catch 블록은 다음과 같습니다.
try {
    _photos.value = MarsApi.retrofitService.getPhotos()
    _status.value = "Success: Mars properties retrieved"
} catch (e: Exception) {
    _status.value = "Failure: ${e.message}"
}

그리드 레이아웃

RecyclerViewGridLayoutManager는 아래와 같이 데이터를 스크롤 가능한 그리드로 배치합니다.

fcf0fc4b78f8650.png

디자인 관점에서 볼 때 그리드 레이아웃은 아이콘이나 이미지로 표현할 수 있는 목록(예: 화성 사진 탐색 앱 내의 목록)에 가장 적합합니다.

그리드 레이아웃에 항목을 배치하는 방법

그리드 레이아웃은 항목을 행과 열의 그리드로 정렬합니다. 세로 스크롤을 사용하는 경우 기본적으로 행의 각 항목은 '스팬' 하나를 차지합니다. 한 항목이 여러 스팬을 차지할 수 있습니다. 아래 예의 경우 한 스팬이 한 열의 너비, 즉 3과 동일합니다.

아래의 두 예에서 각 행은 스팬 세 개로 구성됩니다. 기본적으로 GridLayoutManager는 개발자가 지정한 스팬 수까지 각 항목을 한 스팬에 배치합니다. 스팬 수에 도달하면 다음 줄로 넘어갑니다.

Recyclerview 추가하기

이 단계에서는 단일 이미지 뷰가 아닌 그리드 레이아웃과 함께 Recycler 뷰를 사용하도록 앱의 레이아웃을 변경합니다.

  1. layout/gridview_item.xml을 엽니다. viewModel 데이터 변수를 삭제합니다.
  2. <data> 태그 내부에 MarsPhoto 유형의 다음 photo 변수를 추가합니다.
<data>
   <variable
       name="photo"
       type="com.example.android.marsphotos.network.MarsPhoto" />
</data>
  1. <ImageView>에서 MarsPhoto 객체의 이미지 URL을 참조하도록 app:imageUrl 속성을 변경합니다. 이렇게 변경하면 이 이전 작업에서 적용한 임시 변경사항이 실행취소됩니다.
app:imageUrl="@{photo.imgSrcUrl}"
  1. layout/fragment_overview.xml을 엽니다. 전체 <TextView> 요소를 삭제합니다.
  2. 대신 다음 <RecyclerView> 요소를 추가합니다. ID를 photos_grid로 설정하고 width 속성과 height 속성을 0dp로 설정하여 상위 ConstraintLayout을 채웁니다. 그리드 레이아웃을 사용할 것이므로 layoutManager 속성을 androidx.recyclerview.widget.GridLayoutManager로 설정합니다. 열이 두 개가 되도록 spanCount2로 설정합니다.
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/photos_grid"
    android:layout_width="0dp"
    android:layout_height="0dp"
    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" />
  1. 위의 코드가 Design 뷰에서 어떻게 표시되는지 미리보려면 tools:itemCount를 사용하여 레이아웃에 표시되는 항목의 수를 16으로 설정합니다. itemCount 속성은 Layout Editor에서 Preview 창에 렌더링해야 하는 항목의 수를 지정합니다. tools:listitem을 사용하여 목록 항목의 레이아웃을 grid_view_item으로 설정합니다.
<androidx.recyclerview.widget.RecyclerView
            ...
            tools:itemCount="16"
            tools:listitem="@layout/grid_view_item" />
  1. Design 뷰로 전환하면 다음 스크린샷과 같은 미리보기가 표시됩니다. 화성 사진은 아니지만 recyclerview 그리드 레이아웃이 어떻게 표시되는지 보여줍니다. 미리보기에서는 recyclerview의 모든 단일 그리드 항목에 패딩과 grid_view_item 레이아웃을 사용합니다.

20742824367c3952.png

  1. 머티리얼 디자인 가이드라인에 따라 목록의 상단, 하단, 측면에 8dp의 공간이 있어야 하고 항목 사이에는 4dp의 공간이 있어야 합니다. 이렇게 하려면 fragment_overview.xml 레이아웃과 gridview_item.xml 레이아웃의 패딩 조합을 사용하면 됩니다.

a3561fa85fea7a8f.png

  1. layout/gridview_item.xml을 엽니다. padding 속성에는 이미 항목 외부와 콘텐츠 사이에 2dp의 패딩이 있습니다. 이에 따라 항목 콘텐츠 사이에 4dp의 공간이, 그리고 바깥 가장자리를 따라 2dp의 공간이 확보됩니다. 다시 말해서, 디자인 가이드라인과 일치하도록 바깥 가장자리에 추가로 6dp의 패딩이 필요합니다.
  2. layout/fragment_overview.xml로 돌아갑니다. RecyclerView6dp 패딩을 추가하여 가이드라인에 따라 외부에 8dp를, 내부에 4dp를 확보합니다.
<androidx.recyclerview.widget.RecyclerView
            ...
            android:padding="6dp"
            ...  />
  1. 전체 <RecyclerView> 요소는 다음과 같습니다.
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/photos_grid"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:padding="6dp"
    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"  />

사진 그리드 어댑터 추가하기

이제 fragment_overview 레이아웃에 그리드 레이아웃이 포함된 RecyclerView가 있습니다. 이 단계에서는 웹 서버에서 검색한 데이터를 RecyclerView 어댑터를 통해 RecyclerView에 결합합니다.

ListAdapter(복습)

ListAdapterRecyclerView.Adapter 클래스의 서브클래스로, 백그라운드 스레드의 목록 간 차이를 계산하는 작업을 포함하여 목록 데이터를 RecyclerView에 표시하기 위한 것입니다.

이 앱에서는 ListAdapter.DiffUtil 구현을 사용합니다. DiffUtil을 사용할 때 이점은 RecyclerView에서 일부 항목이 추가되거나 삭제 또는 변경될 때마다 전체 목록이 새로고침되지 않는다는 점입니다. 변경된 항목만 새로고침됩니다.

앱에 ListAdapter를 추가합니다.

  1. overview 패키지에서 PhotoGridAdapter.kt라는 새 Kotlin 클래스를 만듭니다.
  2. 아래와 같이 생성자 매개변수를 사용하여 ListAdapterPhotoGridAdapter 클래스를 확장합니다. PhotoGridAdapter 클래스는 ListAdapter를 확장합니다. 이 생성자에는 목록 항목 유형, 뷰 홀더, DiffUtil.ItemCallback 구현이 필요합니다.
class PhotoGridAdapter : ListAdapter<MarsPhoto,
        PhotoGridAdapter.MarsPhotoViewHolder>(DiffCallback) {
}

요청이 있는 경우 androidx.recyclerview.widget.ListAdapter 클래스와 com.example.android.marsphoto.network.MarsPhoto 클래스를 가져옵니다. 다음 단계에서는 이 생성자에 누락되어 오류를 생성하는 다른 부분을 구현합니다.

  1. 위의 오류를 해결하기 위해 이 단계에서 필요한 메서드를 추가한 후 이 작업의 후반부에서 구현합니다. PhotoGridAdapter 클래스를 클릭하고 빨간색 전구를 클릭한 다음 드롭다운 메뉴에서 Implement members를 선택합니다. 표시되는 팝업에서 ListAdapter 메서드 onCreateViewHolder(), onBindViewHolder()를 선택합니다. 이 작업을 마칠 때 수정되는 오류가 Android 스튜디오에 계속 표시됩니다.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPhotoViewHolder {
   TODO("Not yet implemented")
}

override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPhotoViewHolder, position: Int) {
   TODO("Not yet implemented")
}

onCreateViewHolder 메서드와 onBindViewHolder 메서드를 구현하려면 다음 단계에서 추가할 MarsPhotoViewHolder가 필요합니다.

  1. PhotoGridAdapter 내부에 RecyclerView.ViewHolder를 확장하는 MarsPhotoViewHolder의 내부 클래스 정의를 추가합니다. MarsPhoto를 레이아웃에 결합하기 위한 GridViewItemBinding 변수가 필요하므로, 이 변수를 MarsPhotoViewHolder에 전달합니다. 기본 ViewHolder 클래스는 생성자에 뷰가 있어야 합니다. 이 클래스를 결합 루트 뷰에 전달합니다.
class MarsPhotoViewHolder(private var binding:
                   GridViewItemBinding):
       RecyclerView.ViewHolder(binding.root) {
}

요청이 있는 경우 androidx.recyclerview.widget.RecyclerViewcom.example.android.marsrealestate.databinding.GridViewItemBinding을 가져옵니다.

  1. MarsPhotoViewHolder에서 MarsPhoto 객체를 인수로 사용하고 binding.property를 이 객체로 설정하는 bind() 메서드를 만듭니다. 속성을 설정한 후 executePendingBindings()를 호출하면 업데이트가 즉시 실행됩니다.
fun bind(MarsPhoto: MarsPhoto) {
   binding.photo = MarsPhoto
   binding.executePendingBindings()
}
  1. onCreateViewHolder()PhotoGridAdapter 클래스 내부에서 TODO를 삭제하고 아래의 줄을 추가합니다. onCreateViewHolder() 메서드는 GridViewItemBinding을 확장하고 상위 ViewGroup 컨텍스트의 LayoutInflater를 사용하여 생성된 새 MarsPhotoViewHolder를 반환해야 합니다.
   return MarsPhotoViewHolder(GridViewItemBinding.inflate(
      LayoutInflater.from(parent.context)))

요청이 있는 경우 android.view.LayoutInflater를 가져옵니다.

  1. onBindViewHolder() 메서드에서 TODO를 삭제하고 아래의 줄을 추가합니다. 여기서 getItem()을 호출하여 현재 RecyclerView 위치와 연결된 MarsPhoto 객체를 가져온 다음 이 속성을 MarsPhotoViewHolderbind() 메서드에 전달합니다.
val marsPhoto = getItem(position)
holder.bind(marsPhoto)
  1. PhotoGridAdapter 내부에 아래와 같이 DiffCallback의 컴패니언 객체 정의를 추가합니다.
    DiffCallback 객체는 비교할 일반 객체 유형 MarsPhotoDiffUtil.ItemCallback을 확장합니다. 이 구현 내부에서 두 화성 사진 객체를 비교합니다.
companion object DiffCallback : DiffUtil.ItemCallback<MarsPhoto>() {
}

요청이 있는 경우 androidx.recyclerview.widget.DiffUtil을 가져옵니다.

  1. 빨간색 전구를 눌러 DiffCallback 객체의 비교기 메서드 areItemsTheSame()areContentsTheSame()을 구현합니다.
override fun areItemsTheSame(oldItem: MarsPhoto, newItem: MarsPhoto): Boolean {
   TODO("Not yet implemented")
}

override fun areContentsTheSame(oldItem: MarsPhoto, newItem: MarsPhoto): Boolean {
   TODO("Not yet implemented") }
  1. areItemsTheSame() 메서드에서 TODO를 삭제합니다. 이 메서드는 DiffUtil에서 호출되어 두 객체가 동일한 항목을 나타내는지 여부를 확인합니다. DiffUtil은 이 메서드를 사용하여 새 MarsPhoto 객체가 이전 MarsPhoto 객체와 동일한지 확인합니다. 모든 항목(MarsPhoto 객체)의 ID는 고유합니다. oldItemnewItem의 ID를 비교하여 결과를 반환합니다.
override fun areItemsTheSame(oldItem: MarsPhoto, newItem: MarsPhoto): Boolean {
   return oldItem.id == newItem.id
}
  1. areContentsTheSame()에서 TODO를 삭제합니다. 이 메서드는 두 항목의 데이터가 동일한지 확인하려고 할 때 DiffUtil에서 호출됩니다. MarsPhoto에서 중요한 데이터는 이미지 URL입니다. oldItemnewItem의 URL을 비교하여 결과를 반환합니다.
override fun areContentsTheSame(oldItem: MarsPhoto, newItem: MarsPhoto): Boolean {
   return oldItem.imgSrcUrl == newItem.imgSrcUrl
}

오류 없이 앱을 컴파일하고 실행할 수 있어야 하지만, 에뮬레이터에 빈 화면이 표시됩니다. recyclerview가 준비되었지만 뷰에 전달된 데이터가 없기 때문입니다. 이 데이터는 다음 단계에서 구현합니다.

결합 어댑터를 추가하고 부분 연결하기

이 단계에서는 BindingAdapter를 사용하여 MarsPhoto 객체 목록으로 PhotoGridAdapter를 초기화합니다. BindingAdapter를 사용하여 RecyclerView 데이터를 설정하면 데이터 결합이 자동으로 MarsPhoto 객체 목록의 LiveData를 관찰합니다. 그런 다음 MarsPhoto 목록이 변경되면 결합 어댑터가 자동으로 호출됩니다.

  1. BindingAdapters.kt를 엽니다.
  2. 파일 끝에 RecyclerViewMarsPhoto 객체 목록을 인수로 사용하는 bindRecyclerView() 메서드를 추가합니다. 이 메서드에 listData 속성이 포함된 @BindingAdapter 주석을 추가합니다.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
    data: List<MarsPhoto>?) {
}

요청이 있는 경우 androidx.recyclerview.widget.RecyclerViewcom.example.android.marsphotos.network.MarsPhoto를 가져옵니다.

  1. bindRecyclerView() 함수 내부에서 recyclerView.adapterPhotoGridAdapter로 변환하여 새 val 속성 adapter.에 할당합니다.
val adapter = recyclerView.adapter as PhotoGridAdapter
  1. bindRecyclerView() 함수 끝부분에서 화성 사진 목록 데이터가 포함된 adapter.submitList()를 호출합니다. 그러면 새 목록을 사용할 수 있을 때 RecyclerView에 알려줍니다.
adapter.submitList(data)

요청이 있는 경우 com.example.android.marsrealestate.overview.PhotoGridAdapter를 가져옵니다.

  1. 전체 bindRecyclerView 결합 어댑터는 다음과 같습니다.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
                    data: List<MarsPhoto>?) {
   val adapter = recyclerView.adapter as PhotoGridAdapter
   adapter.submitList(data)

}
  1. 모든 항목을 연결하려면 res/layout/fragment_overview.xml을 엽니다. RecyclerView 요소에 app:listData 속성을 추가하고 데이터 결합을 사용하여 이 속성을 viewmodel.photos로 설정합니다. 이전 작업에서 ImageView에 실행한 작업과 유사합니다.
app:listData="@{viewModel.photos}"
  1. overview/OverviewFragment.kt를 엽니다. onCreateView()return 문 바로 앞에서 binding.photosGridRecyclerView 어댑터를 새 PhotoGridAdapter 객체로 초기화합니다.
binding.photosGrid.adapter = PhotoGridAdapter()
  1. 앱을 실행합니다. 화성 이미지를 스크롤하는 그리드가 표시됩니다. 스크롤하면 새 이미지가 표시되지만 약간 이상해 보입니다. 스크롤하는 동안 RecyclerView의 상단과 하단에 패딩이 유지되므로 목록이 작업 모음 아래에서 스크롤되는 것처럼 보이지 않습니다.

5d03641aa1589842.png

  1. 수정하려면 android:clipToPadding 속성을 사용해 내부 콘텐츠를 패딩에 맞춰 자르지 않도록 RecyclerView에 알려야 합니다. 그러면 스크롤 뷰가 패딩 영역 안에 그려집니다. layout/fragment_overview.xml로 돌아갑니다. RecyclerViewandroid:clipToPadding 속성을 추가하고 false로 설정합니다.
<androidx.recyclerview.widget.RecyclerView
            ...
            android:clipToPadding="false"
            ...  />
  1. 앱을 실행합니다. 예상대로 이미지 자체가 표시되기 전에 로드 진행률 아이콘이 표시되는 것도 볼 수 있습니다. 이 아이콘은 Coil 이미지 라이브러리에 전달한 자리표시자 로드 이미지입니다.

3128b84aa22ef97e.png

  1. 앱이 실행되는 동안 비행기 모드를 사용 설정합니다. 에뮬레이터에서 이미지를 스크롤합니다. 아직 로드되지 않은 이미지는 손상 이미지 아이콘으로 표시됩니다. 이 아이콘은 네트워크 오류가 발생하거나 이미지를 가져올 수 없을 때 표시하도록 Coil 이미지 라이브러리에 전달한 이미지 드로어블입니다.

28d2cbba564f35ff.png

축하합니다. 거의 완료되었습니다. 다음이자 마지막 작업에서는 앱에 더 많은 오류 처리를 추가하여 사용자 경험을 더욱 개선합니다.

이미지를 가져올 수 없을 때 MarsPhotos 앱은 손상 이미지 아이콘을 표시합니다. 하지만 네트워크가 없으면 앱에서 빈 화면이 표시됩니다. 빈 화면은 다음 단계에서 확인합니다.

  1. 기기나 에뮬레이터에서 비행기 모드를 사용 설정합니다. Android 스튜디오에서 앱을 실행합니다. 빈 화면이 표시됩니다.

492011786c2dd7f7.png

이는 만족스러운 사용자 경험이 아닙니다. 이 작업에서는 기본 오류 처리를 추가하여 사용자가 현재 상황을 더 잘 파악하도록 합니다. 앱은 인터넷을 사용할 수 없는 경우 연결 오류 아이콘을 표시하고 MarsPhoto 목록을 가져오는 동안에는 로드 애니메이션을 표시합니다.

ViewModel에 상태 추가하기

이 작업에서는 OverviewViewModel에서 웹 요청의 상태를 나타내는 속성을 만듭니다. 로드, 성공, 실패 등 세 가지 상태를 고려합니다. 로드 상태는 데이터를 기다리는 동안 발생합니다. 성공 상태는 웹 서비스에서 데이터를 성공적으로 검색했음을 나타냅니다. 실패 상태는 네트워크 오류나 연결 오류를 나타냅니다.

Kotlin의 enum 클래스

애플리케이션에서 이 세 가지 상태를 나타내려면 enum을 사용합니다. enum은 열거의 단축형으로, 컬렉션의 모든 항목을 순서가 지정된 목록으로 나열한다는 의미입니다. 각 enum 상수는 enum 클래스의 객체입니다.

Kotlin에서 enum은 상수 집합을 보유할 수 있는 데이터 유형입니다. 아래와 같이 클래스 정의 앞에 키워드 enum을 추가하여 정의합니다. 열거형 상수는 쉼표로 구분됩니다.

정의:

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

사용:

var direction = Direction.NORTH;

위와 같이 enum 객체 참조는 클래스 이름 뒤에 점(.) 연산자와 상수 이름을 사용하여 처리할 수 있습니다.

ViewModel에서 상태 값과 함께 enum 클래스 정의를 추가합니다.

  1. overview/OverviewViewModel.kt를 엽니다. 파일 상단에서(가져오기 뒤, 클래스 정의 앞에) enum을 추가하여 사용 가능한 모든 상태를 나타냅니다.
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. _status 속성과 status 속성의 정의로 스크롤하고 유형을 String에서 MarsApiStatus. MarsApiStatus로 변경합니다. MarsApiStatus는 이전 단계에서 정의한 enum 클래스입니다.
private val _status = MutableLiveData<MarsApiStatus>()

val status: LiveData<MarsApiStatus> = _status
  1. getMarsPhotos() 메서드에서 "Success: ..." 문자열을 MarsApiStatus.DONE 상태로, "Failure..." 문자열을 MarsApiStatus.ERROR로 변경합니다.
try {
    _photos.value = MarsApi.retrofitService.getPhotos()
    _status.value = MarsApiStatus.DONE
} catch (e: Exception)
     _status.value = MarsApiStatus.ERROR
}
  1. 상태를 try {} 블록 위에서 MarsApiStatus.LOADING으로 설정합니다. 이 상태는 코루틴을 실행하는 동안 데이터를 기다릴 때 초기 상태입니다. 이제 전체 viewModelScope.launch {} 블록은 다음과 같습니다.
viewModelScope.launch {
            _status.value = MarsApiStatus.LOADING
            try {
                _photos.value = MarsApi.retrofitService.getPhotos()
                _status.value = MarsApiStatus.DONE
            } catch (e: Exception) {
                _status.value = MarsApiStatus.ERROR
            }
        }
  1. catch {} 블록에서 오류 상태 다음에 _photos를 빈 목록으로 설정합니다. 이렇게 하면 Recycler 뷰가 삭제됩니다.
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
   _photos.value = listOf()
}
  1. 전체 getMarsPhotos() 메서드는 다음과 같습니다.
private fun getMarsPhotos() {
   viewModelScope.launch {
        _status.value = MarsApiStatus.LOADING
        try {
           _photos.value = MarsApi.retrofitService.getPhotos()
           _status.value = MarsApiStatus.DONE
        } catch (e: Exception) {
           _status.value = MarsApiStatus.ERROR
           _photos.value = listOf()
        }
    }
}

상태를 나타내는 enum 상태를 정의하고 코루틴 시작 시 로드 상태를 설정했습니다. 앱이 완료되어 웹 서버에서 데이터를 검색하면 완료로 설정되고, 예외가 있으면 오류로 설정됩니다. 다음 작업에서는 결합 어댑터를 사용하여 해당하는 아이콘을 표시합니다.

상태 ImageView용 결합 어댑터 추가하기

일련의 enum 상태를 사용하여 OverviewViewModel에서 MarsApiStatus를 설정했습니다. 이 단계에서는 상태를 앱에 표시합니다. ImageView에 결합 어댑터를 사용하여 로드 상태 및 오류 상태의 아이콘을 표시합니다. 앱이 로드 상태이거나 오류 상태일 때 ImageView가 표시됩니다. 앱에서 로드가 완료되면 ImageView가 표시되지 않습니다.

  1. BindingAdapters.kt를 열고 파일의 끝으로 스크롤하여 다른 어댑터를 추가합니다. ImageView 값과 MarsApiStatus 값을 인수로 사용하는 bindStatus()라는 새 결합 어댑터를 추가합니다. 맞춤 속성 marsApiStatus를 매개변수로 전달하는 @BindingAdapter 주석을 메서드에 추가합니다.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
          status: MarsApiStatus?) {
}

요청이 있는 경우 com.example.android.marsrealestate.overview.MarsApiStatus를 가져옵니다.

  1. bindStatus() 메서드 내부에 when {} 블록을 추가하여 서로 다른 상태 간에 전환합니다.
when (status) {

}
  1. when {} 내부에 로드 상태(MarsApiStatus.LOADING)의 사례를 추가합니다. 이 상태의 경우 ImageView를 visible로 설정하고 로드 애니메이션에 할당합니다. 이전 작업에서 Coil에 사용한 것과 동일한 애니메이션 드로어블입니다.
when (status) {
   MarsApiStatus.LOADING -> {
      statusImageView.visibility = View.VISIBLE
      statusImageView.setImageResource(R.drawable.loading_animation)
   }
}

요청이 있는 경우 android.view.View를 가져옵니다.

  1. 오류 상태(MarsApiStatus.ERROR)의 사례를 추가합니다. LOADING 상태의 경우와 유사하게 상태 ImageView를 visible로 설정하고 연결 오류 드로어블을 사용합니다.
MarsApiStatus.ERROR -> {
   statusImageView.visibility = View.VISIBLE
   statusImageView.setImageResource(R.drawable.ic_connection_error)
}
  1. 완료 상태(MarsApiStatus.DONE)의 사례를 추가합니다. 여기서는 성공적인 응답이 있으므로 상태 ImageView의 공개 상태를 View.GONE으로 설정하여 숨깁니다.
MarsApiStatus.DONE -> {
   statusImageView.visibility = View.GONE
}

상태 이미지 뷰의 결합 어댑터를 설정했습니다. 다음 단계에서는 새 결합 어댑터를 사용하는 이미지 뷰를 추가합니다.

상태 ImageView 추가하기

이 단계에서는 이전에 정의한 상태를 표시하는 이미지 뷰를 fragment_overview.xml에 추가합니다.

  1. res/layout/fragment_overview.xml을 엽니다. ConstraintLayoutRecyclerView 요소 아래에 다음과 같이 ImageView를 추가합니다.
<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}" />

위의 ImageView에는 RecyclerView와 동일한 제약 조건이 있습니다. 그러나 이미지를 늘려 뷰를 채우는 대신, 너비와 높이가 wrap_content를 사용하여 이미지를 중앙에 배치합니다. 또한 app:marsApiStatus 속성이 viewModel.status로 설정되어 있어, ViewModel의 상태 속성이 변경되면 BindingAdapter가 호출됩니다.

  1. 위 코드를 테스트하려면 에뮬레이터나 기기에서 비행기 모드를 사용 설정하여 네트워크 연결 오류를 시뮬레이션합니다. 앱을 컴파일하고 실행하면 오류 이미지가 표시됩니다.

a91ddb1c89f2efec.png

  1. Back 버튼을 탭하여 앱을 닫고 비행기 모드를 사용 중지합니다. 최근 항목 화면을 사용하여 앱을 반환합니다. 네트워크 연결 속도에 따라 앱이 웹 서비스를 쿼리할 때 이미지 로드가 시작되기 전에 로드 스피너가 아주 잠시 표시될 수도 있습니다.

이 Codelab을 완료하고 MarsPhotos 앱을 빌드한 것을 축하합니다! 이제 가족과 친구들에게 실제 화성 사진이 담긴 앱을 자랑하세요.

이 Codelab의 솔루션 코드는 아래 표시된 프로젝트에 있습니다. main 분기를 사용하여 코드를 가져오거나 다운로드하세요.

이 Codelab의 코드를 가져와서 Android 스튜디오에서 열려면 다음을 실행합니다.

코드 가져오기

  1. 제공된 URL을 클릭합니다. 브라우저에서 프로젝트의 GitHub 페이지가 열립니다.
  2. 프로젝트의 GitHub 페이지에서 Code 버튼을 클릭하여 대화상자를 엽니다.

Eme2bJP46u-pMpnXVfm-bS2N2dlyq6c0jn1DtQYqBaml7TUhzXDWpYoDI0lGKi4xndE_uJw8sKfwfOZ1fC503xCVZrbh10JKJ4iEHdLDwFfdvnOheNxkokITW1LW6UZTncVJJUZ5Fw

  1. 대화상자에서 Download ZIP 버튼을 클릭하여 컴퓨터에 프로젝트를 저장합니다. 다운로드가 완료될 때까지 기다립니다.
  2. 컴퓨터에서 파일을 찾습니다(예: Downloads 폴더).
  3. ZIP 파일을 더블클릭하여 압축을 해제합니다. 프로젝트 파일이 포함된 새 폴더가 만들어집니다.

Android 스튜디오에서 프로젝트 열기

  1. Android 스튜디오를 시작합니다.
  2. Welcome to Android Studio 창에서 Open an existing Android Studio project를 클릭합니다.

Tdjf5eS2nCikM9KdHgFaZNSbIUCzKXP6WfEaKVE2Oz1XIGZhgTJYlaNtXTHPFU1xC9pPiaD-XOPdIxVxwZAK8onA7eJyCXz2Km24B_8rpEVI_Po5qlcMNN8s4Tkt6kHEXdLQTDW7mg

참고: Android 스튜디오가 이미 열려 있는 경우 File > New > Import Project 메뉴 옵션을 대신 선택합니다.

PaMkVnfCxQqSNB1LxPpC6C6cuVCAc8jWNZCqy5tDVA6IO3NE2fqrfJ6p6ggGpk7jd27ybXaWU7rGNOFi6CvtMyHtWdhNzdAHmndzvEdwshF_SG24Le01z7925JsFa47qa-Q19t3RxQ

  1. Import Project 대화상자에서 압축 해제된 프로젝트 폴더가 있는 위치로 이동합니다(예: Downloads 폴더).
  2. 프로젝트 폴더를 더블클릭합니다.
  3. Android 스튜디오가 프로젝트를 열 때까지 기다립니다.
  4. Run 버튼 j7ptomO2PEQNe8jFt4nKCOw_Oc_Aucgf4l_La8fGLCMLy0t9RN9SkmBFGOFjkEzlX4ce2w2NWq4J30sDaxEe4MaSNuJPpMgHxnsRYoBtIV3-GUpYYcIvRJ2HrqR27XGuTS4F7lKCzg을 클릭하여 앱을 빌드하고 실행합니다. 예상대로 작동하는지 확인합니다.
  5. Project 도구 창에서 프로젝트 파일을 살펴보고 앱이 구현된 방식을 확인합니다.
  • Coil 라이브러리는 앱에서 이미지 다운로드, 버퍼링, 디코딩, 캐시와 같은 이미지 관리 프로세스를 단순화합니다.
  • 결합 어댑터는 뷰와 이 뷰에 결합된 데이터 사이에 있는 확장 메서드입니다. 결합 어댑터는 데이터가 변경될 때(예: Coil을 호출하여 URL에서 ImageView로 이미지 로드하기) 맞춤 동작을 제공합니다.
  • 결합 어댑터는 @BindingAdapter 주석이 추가된 확장 메서드입니다.
  • 이미지의 그리드를 표시하려면 GridLayoutManager와 함께 RecyclerView를 사용합니다.
  • 변경 시 속성 목록을 업데이트하려면 RecyclerView와 레이아웃 사이에 결합 어댑터를 사용합니다.

Android 개발자 문서:

기타: