코드랩 Retrofit으로 데이터 가져오기2
지난번에 Retrofit과 Moshi라이브러리로 데이터 통신 성공까지 만들었다. 이번엔 통신한 데이터를 RecyclerView(+LiveData, ViewModel, DataBinding, DiffUtil 전부 사용해서 !)로 띄워주는것 까지 구현!
리싸이클러뷰에 띄울 item 레이아웃 만들기
일단 데이터를 가져와 img_src를 이미지뷰로 띄워주기만 하기때문에 item 레이아웃에는 imageView만 있으면 된다.
그리고 아이템하나는 MarsProperty객체 하나를 띄워주는거기 때문에 MarsProperty DataBinding을 해준다.
grid_view_item.xml
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019, The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. ~ -->
<layout 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">
<data> <variable name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
</data> <ImageView android:id="@+id/mars_image"
android:layout_width="match_parent"
android:layout_height="170dp"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
imageUrl="@{property.imgSrcUrl}"
android:padding="2dp"
tools:src="@tools:sample/backgrounds/scenic"/>
</layout>
리싸이클러뷰를 띄울 화면에 리싸이클러뷰를 넣어준다.
전체화면에 리싸이클러뷰를 보여줄거기때문에 match_parent로 해주고, 한 행에 2개씩 보여줄거기때문에 spanCount=2, GridLayoutManager를 설정해준다.
그리고 여기엔 서버통신으로 받아온 MarsProperty 리스트에 접근해야하기때문에 viewModel을 바인딩 해준다.
밑의 imageView는 데이터 통신 상태에따라 보여줄 이미지 !
fragment_overview.xml
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019, The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. ~ -->
<layout 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">
<data> <variable name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.android.marsrealestate.MainActivity">
<androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_photo_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:listData="@{viewModel.properties}"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />
<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}" />
</androidx.constraintlayout.widget.ConstraintLayout></layout>
리싸이클러뷰를 만들었으니 Adapter와 ViewHolder를 만들어준다.
먼저 DiffUtil을 상속받는 DiffCallback을 정의해준다.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}
}
데이터바인딩과 DiffUtil을 사용한다.
ListAdapter<객체타입, 뷰홀더>(DiffCallback)상속받는 어댑터를 생성하고, 필요한 메소드를 오버라이드 해준다.
class PhotoGridAdapter : ListAdapter<MarsProperty, PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback){
...
}
onCreateViewHolder로 바인딩.inflate~ 로 뷰홀더에 넘겨준다.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MarsPropertyViewHolder {
return MarsPropertyViewHolder(GridViewItemBinding.inflate(LayoutInflater.from(parent.context)))
}
onBindViewHolder 에서는 ListAdapter의 getItem을 사용해서 holder.bind로 해당 포지션의 아이템을 넘겨준다.
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
val marsProperty = getItem(position)
holder.bind(marsProperty)
}
아까 onCreateViewHolder에서 binding 을 인수로 넘겼기 때문에 변수로 받아주고, onBindViewHolder에서 bind함수의 인수로 MarsProperty 객체를 넘겼기 때문에 받아서 grid_view_item.xml에 써줬던 property에 값을 넣어준다.
class MarsPropertyViewHolder(private var binding:GridViewItemBinding):RecyclerView.ViewHolder(binding.root){
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
}}
PhotoGridAdapter
/*
* Copyright 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */
package com.example.android.marsrealestate.overview
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.android.marsrealestate.databinding.GridViewItemBinding
import com.example.android.marsrealestate.network.MarsProperty
class PhotoGridAdapter : ListAdapter<MarsProperty, PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MarsPropertyViewHolder {
return MarsPropertyViewHolder(GridViewItemBinding.inflate(LayoutInflater.from(parent.context)))
}
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
val marsProperty = getItem(position)
holder.bind(marsProperty)
}
class MarsPropertyViewHolder(private var binding:GridViewItemBinding):RecyclerView.ViewHolder(binding.root){
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
} }
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}
}}
만든 어댑터 리싸이클러뷰에 적용
OverviewFragment.kt
binding.rvPhotoList.adapter = PhotoGridAdapter()
BindingAdapter 사용하기
마지막으로 서버통신으로 받아온 데이터를 adapter 에 바인딩해주면 된다.
@BindingAdpater(“xml에서 사용할 이름”)
fun 함수이름(적용할 view, view에 적용할 값)
{ 어떻게 적용할지! }
이렇게 이해하면 편한것 같다.
BindingAdpaters.kt
/*
* Copyright 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */
package com.example.android.marsrealestate
import android.view.View
import android.widget.ImageView
import androidx.core.net.toUri
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.example.android.marsrealestate.network.MarsApi
import com.example.android.marsrealestate.network.MarsProperty
import com.example.android.marsrealestate.overview.MarsApiStatus
import com.example.android.marsrealestate.overview.PhotoGridAdapter
//String 이미지 주소 Glide로 띄워주기 //placeholder -> 통신 길어질때 보여질 모습
//error -> 네트워크 끊겼을때 보여질 모습 @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)
}
}
//서버통신으로 받아온 객체리스트 어댑터에 값 설정해주기 @BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, data: List<MarsProperty>?) {
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
}
//OverviewFragment에 적용해줬던 이미지 처리 -> 네트워크에따라 보이게할지 여부 판단 @BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,status:MarsApiStatus?){
when(status){
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
} MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
} }}
참고
Comments