[Android/Kotlin] 도서 리뷰 앱 - RecyclerView로 아이템 그리기(Glide사용하여 이미지 로딩)
recyclerview란?!
비슷한 형식의 뷰는 몇개만 그려놓고 스크롤 내리면 위의 뷰를 없애고 미리 그려진 뷰에 데이터를 할당
리스트뷰랑 비슷하지만 recyclerview는 한번 바인딩을 해주면 나중에 재사용가능
1) recyclerView를 레이아웃에 추가하고 각 아이템뷰 레이아웃 추가
2) adapter 구현 (recyclerView에 데이터 연결)
3) adapter, layoutManager 지정
recyclerView 구성요소
Adapter : 데이터 목록을 아이템 단위의 뷰로 구성하여 화면에 표시(한 세트로 묶어서 포장하는 역할)
LayoutManager : 아이템뷰가 나열되는 형태를 관리하기 위한 요소제공(포장한 걸 어떻게 쌓을지 정하는 역할)
ViewHolder : 화면에 표시될 아이템뷰를 저장하는 객체
저번 글에서 아이템별 레이아웃 설정까지 해줬다.
- adapter 구현 ( -> 한 세트로 포장하는 역할)
class BookAdapter: ListAdapter<Book, BookAdapter.BookItemViewHolder> (diffUtil){
inner class BookItemViewHolder(private val binding: ItemBookBinding): RecyclerView.ViewHolder(binding.root){
fun bind(bookModel: Book){
binding.titleTextView.text = bookModel.title.replace("<b>","").replace("</b>","")
binding.descriptionTextView.text = bookModel.description.replace("<b>","").replace("</b>","")
Glide
.with(binding.coverImageView.context)
.load(bookModel.image)
.into(binding.coverImageView)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookItemViewHolder {
return BookItemViewHolder(ItemBookBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: BookItemViewHolder, position: Int) {
holder.bind(currentList[position])
}
companion object {
val diffUtil = object : DiffUtil.ItemCallback<Book>(){
override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean {
return oldItem.title == newItem.title
}
}
}
}
먼저 뷰바인딩을 해주기 위해 gradle에 들어가서 활성화해준다
viewBinding{
enabled = true
}
class BookAdapter: ListAdapter<Book, BookAdapter.BookItemViewHolder> (diffUtil){
inner class BookItemViewHolder(private val binding: ItemBookBinding): RecyclerView.ViewHolder(binding.root){
fun bind(bookModel: Book){
binding.titleTextView.text = bookModel.title.replace("<b>","").replace("</b>","")
binding.descriptionTextView.text = bookModel.description.replace("<b>","").replace("</b>","")
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookItemViewHolder {
return BookItemViewHolder(ItemBookBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: BookItemViewHolder, position: Int) {
holder.bind(currentList[position])
}
}
inner class로 BookItemViewHolder를 만들어주고
implement members로 onCreateViewHolder와 onBindViewHolder를 추가해준다
onCreateViewHolder는 미리 만들어진 ViewHolder가 없을 경우 새로 생성하고 뷰홀더 객체에 담아 리턴(틀만들기)
onBindViewHolder는 ViewHolder가 뷰에 그려지게 되었을 때 데이터를 bind하는 함수(틀에 내용 채우기)
ViewHolder 패턴은 각 뷰의 객체를 ViewHolder에 보관함으로써 뷰의 내용을 업데이트하기 위한
findViewById() 호출을 줄여 효과적으로 퍼포먼스 개선을 할 수 있는 패턴
ViewHolder 패턴을 사용하면 한번 생성하여 저장했던 뷰는 다시 findViewById()로 뷰를 불러올 필요가 x
class BookAdapter: ListAdapter<Book, BookAdapter.BookItemViewHolder> (diffUtil){
inner class BookItemViewHolder(private val binding: ItemBookBinding): RecyclerView.ViewHolder(binding.root){
fun bind(bookModel: Book){ ②
binding.titleTextView.text = bookModel.title.replace("<b>","").replace("</b>","")
binding.descriptionTextView.text = bookModel.description.replace("<b>","").replace("</b>","")
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookItemViewHolder {
return BookItemViewHolder(ItemBookBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: BookItemViewHolder, position: Int) {
holder.bind(currentList[position]) ①
}
}
api 데이터는 이미 currentList에 저장이 되어있고, position 변수를 이용해 데이터를 바인딩할 수 있다
onBindViewHolder에서 데이터를 바인딩해주기 위해서 BookItemViewHolder에 bind함수를 생성한다.
텍스트는 .text로 쉽게 바인딩 할 수 있다
네이버 검색api를 사용하니까 검색어에 <b>keyword</b> 이렇게 붙어서 제거해주기 위해
replace("<b>","").replace("</b>","")를 뒤에 추가했다
class BookAdapter: ListAdapter<Book, BookAdapter.BookItemViewHolder> (diffUtil){
inner class BookItemViewHolder(private val binding: ItemBookBinding): RecyclerView.ViewHolder(binding.root){
fun bind(bookModel: Book){ ②
binding.titleTextView.text = bookModel.title.replace("<b>","").replace("</b>","")
binding.descriptionTextView.text = bookModel.description.replace("<b>","").replace("</b>","")
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookItemViewHolder {
return BookItemViewHolder(ItemBookBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: BookItemViewHolder, position: Int) {
holder.bind(currentList[position]) ①
}
}
이부분은 먼가 어렵네,, 설명을 못하겠다,, 공부를 좀 더 한 후에 ,,,, 다시 돌아오겠서,,
class BookAdapter: ListAdapter<Book, BookAdapter.BookItemViewHolder> (diffUtil){
inner class BookItemViewHolder(private val binding: ItemBookBinding): RecyclerView.ViewHolder(binding.root){
fun bind(bookModel: Book){
binding.titleTextView.text = bookModel.title.replace("<b>","").replace("</b>","")
binding.descriptionTextView.text = bookModel.description.replace("<b>","").replace("</b>","")
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookItemViewHolder {
return BookItemViewHolder(ItemBookBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: BookItemViewHolder, position: Int) {
holder.bind(currentList[position])
}
companion object {
val diffUtil = object : DiffUtil.ItemCallback<Book>(){
override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean {
return oldItem.title == newItem.title
}
}
}
}
diffUtil은 리싸이클러뷰가 실제로 뷰의 포지션이 변경되었을 때 새로운 값을 할당할지 말지 결정하는 것
areItemsTheSame 아이템이 같은가
areContentsTheSame 안에있는 컨텐츠가 같은가
- Glide 사용하기
Glide는 안드로이드에서 이미지를 빠르고 효율적으로 불러올 수 있게 도와주는 라이브러리
Glide를 사용하기 위해서 gradle에 추가해준다
implementation 'com.github.bumptech.glide:glide:4.13.0'
뷰에 이미지 로딩하기
class BookAdapter: ListAdapter<Book, BookAdapter.BookItemViewHolder> (diffUtil){
inner class BookItemViewHolder(private val binding: ItemBookBinding): RecyclerView.ViewHolder(binding.root){
fun bind(bookModel: Book){
binding.titleTextView.text = bookModel.title.replace("<b>","").replace("</b>","")
binding.descriptionTextView.text = bookModel.description.replace("<b>","").replace("</b>","")
Glide
.with(binding.coverImageView.context)
.load(bookModel.image)
.into(binding.coverImageView)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookItemViewHolder { ... }
override fun onBindViewHolder(holder: BookItemViewHolder, position: Int) { ... }
companion object { ... }
}
with() : View, Fragment 혹은 Activity로부터 Context를 가져온다
load() : 이미지를 로드한다. 다양한 방법으로 이미지를 불러올 수 있다
into() : 이미지를 보여줄 View를 지정한다
- recyclerView에 adapter와 layoutManager 지정
private fun initBookRecyclerView(){
adapter = BookAdapter()
binding.bookRecyclerView.layoutManager = LinearLayoutManager(this)
binding.bookRecyclerView.adapter = adapter
}
adapter.submitList(response.body()?.books.orEmpty())
onResponse 함수 내에서 submitList로 currentList(bookAdapter 전달 인자)에 데이터를 넣어준다
- 이벤트 처리
binding.searchEditText.setOnKeyListener{v, keyCode, event ->
if(keyCode == KeyEvent.KEYCODE_ENTER && event.action == MotionEvent.ACTION_DOWN) {
search(binding.searchEditText.text.toString())
return@setOnKeyListener true
}
return@setOnKeyListener false
}
setOnKeyListener는 키보드의 key가 눌렸을 때 실행되는 리스너
조건문 안에는 Enter 키가 눌렸을 때의 조건을 작성해준다
이벤트가 발생하면 전에 만들어둔 search함수를 호출하고 반환값은 true (내가 이벤트 처리했음을 의미)
나머지의 경우는 false를 반환한다
참고