Binding 사용하여 RecyclerView 만들기
Binding을 사용하여 생각보다 뚝딱뚝딱 RecyclerView를 만들어볼 수 있다.
안드로이드는 (아마도) 앞으로 할 예정이 없고, 할 마음도 없기 때문에 어떻게 구현되는 건지만 살펴본다.
RecyclerView는 개별 요소를 재활용하여 사용하는 것이다.
스크롤하여 화면에서 벗어나더라도 뷰를 제거하지 않고, 스크롤된 뷰를 재사용한다.
리사이클러뷰에서는 뷰의 데이터를 바인딩하기 위해 Adapter를 사용한다.
위와 같이 RecyclerView 두 개가 있는 있는 화면을 만들 것이다.
build.gradle (:app)
안드로이드에서는 설정이 제일제일제일 중요하다. 버전도 중요하다.
build.gradle에서는 안드로이드 앱이나 라이브러리를 컴파일, 빌드 및 패키징에 대해 정의한다. 프로젝트 수준, 앱 수준의 설정 파일이 개별로 존재하는데, 현재는 앱 수준의 설정 파일만 수정할 것이다.
Binding 사용을 위해 dataBinding을 추가해줄 것이다.
android {} 내 부분에 dataBinding 설정을 추가한다.
dataBinding {
enabled = true
}
DataBinding은 선언적 형식으로 레이아웃의 UI 구성요소를 앱의 데이터 소스와 결합할 수 있는 지원 라이브러리이다.
(ViewBinding 사용 시 바인딩 클래스의 인스턴스에 상응하는 레이아웃에 ID가 있는 모든 뷰를 직접 참조할 수 있다.)
gradle 파일을 수정했으면 무조건 Sync Now를 클릭하여 적용시켜주어야 한다.
이걸 안 한 적이 한두 번이 아니다 🤣
Adapter
우선 데이터 설정을 위해 Adapter를 생성해준다.
MainActivity가 있는 패키지 내에 adapter 패키지를 생성해준 뒤 CustomAdapter 코틀린 파일을 생성하였다.
리사이클러뷰를 사용할 것이므로 이 어댑터는 리사이클러뷰의 어댑터를 상속받는다.
package com.example.myapplication.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.myapplication.R
class CustomAdapter : RecyclerView.Adapter<CustomAdapter.CustomViewHolder>() {
private val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
return CustomViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_list, parent, false)
)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.bind(list[position])
}
override fun getItemCount(): Int {
return list.size
}
class CustomViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val txtView: TextView = view.findViewById(R.id.item_name)
fun bind(position: Int) {
txtView.text = "TEXT $position"
}
}
}
RecyclerView.Adapter<CustomAdapter.CustomViewHolder>()를 상속받았기 때문에 onCreateViewHolder() 함수와 onBindViewHolder(), getItemCount() 함수를 오버라이드 한다.
또한 RecyclerView.ViewHolder() 클래스를 상속받은 CustomViewHolder() 클래스를 생성한다.
위 함수와 클래스를 작성한 것까지가 리사이클러뷰 커스텀 어댑터의 기본 형태이다.
onCreateViewHolder() 함수의 리턴 값은 CustomViewHolder이다. 매개변수로 view가 들어간다.
LayoutInflater.from(parent.context).inflate(R.layout.[레이아웃명], parent, false)가 -view 자리의-인자로 들어간다.
아직 레이아웃 파일을 생성하지 않았기 때문에 코드상으로 R.layout.item_list 부분에서 오류가 발생한다.
R 클래스는 안드로이드 개발 시 자동으로 생성된다. layout 내 요소들을 생성하면 자동으로 등록된다.
R 클래스를 import 하고 싶다면 [내 앱 패키지].R을 import 하면 된다. 처음 안드로이드 개발을 할 땐 이걸 몰랐어서 조금 고생했었다.
onBindViewHolder() 함수에서는 각 요소에 데이터를 연결한다.
요소와 데이터 연결 시 CustomViweHolder 클래스 내에 bind()라는 함수를 만들어 이를 사용한다.
CustomViewHolder에서 레이아웃 내에 있는 요소 값을 가져와, bind() 함수 내에서 해당 요소에 데이터 값을 집어넣는다.
리사이클러뷰를 구현하기 때문에 position 값은 따로 지정해주지 않아도 자동으로 들어간다.
getItemCount() 함수는 전체 아이템 개수를 리턴한다.
xml 생성
이제 레이아웃을 생성해본다.
앞서 R 클래스를 사용하여 item_list에 접근하려 했으니 레이아웃 파일명은 item_list로 작성하였다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="70dp"
android:layout_height="90dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/item_img"
android:layout_width="50dp"
android:layout_height="50dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/item_name"
android:src="@drawable/ic_launcher_foreground"
android:background="@drawable/ic_launcher_background"/>
<TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="이름"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/item_img"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
ImageView에는 데이터 값을 지정하지 않고 기본 안드로이드 아이콘을 넣을 것이므로 src와 background를 기존 drawable 폴더 내에 있는 ic 파일로 지정해주었다.
id가 item_name인 TextView 요소의 데이터를 어댑터에서 설정해줄 것이다.
이로써 데이터를 요소에 집어넣기 위한 처리는 끝났다.
이제 리사이클러뷰를 구현해본다.
Activity
Activity는 사용자와 상호작용하기 위한 진입점으로, 앱의 UI를 그리는 창을 제공한다.
package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.myapplication.adapter.CustomAdapter
import com.example.myapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var cAdapter: CustomAdapter
private lateinit var horizontalManager: LinearLayoutManager
private lateinit var verticalManager: LinearLayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
cAdapter = CustomAdapter()
// Horizontal
horizontalManager = LinearLayoutManager(this@MainActivity)
horizontalManager.orientation = LinearLayoutManager.HORIZONTAL
binding.recyclerHorizontal.apply {
layoutManager = horizontalManager
adapter = cAdapter
}
// Vertical
verticalManager = LinearLayoutManager(this@MainActivity)
verticalManager.orientation = LinearLayoutManager.VERTICAL
binding.recyclerVertical.apply {
layoutManager = verticalManager
adapter = cAdapter
}
}
}
처음 EmptyActivity 생성 시 onCreate 부분은 아래와 같이 되어있다.
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
여기서는 바인딩을 쓰기 때문에 binding을 설정하고, ContentView는 R.layout.activity_main이 아닌 binding.root로 설정해준다.
앞서 그래들에서 dataBinding을 사용한다고 설정했기 때문에 ActivityMainBinding은 [내 앱 패키지].databinding 안에 생성되어 있다.
만약 현재 액티비티 이름이 예를 들어 CustomActivity와 같이 되어있다면 바인딩 객체의 이름이 ActivityMainBinding이 아닌, ActivityCustomBinding으로 되어있을 것이다.
앞 전에 생성한 CustomAdapter를 가져온다. 리사이클러뷰의 데이터를 바인딩하기 위하여 어댑터를 사용한다고 하였으니, 미리 만들어놓은 CustomAdapter를 사용할 것이다.
LinearLayoutManager는 수평, 수직으로 처리하는 레이아웃 매니저이다.
이 외에도 GridLayoutManager를 사용하여 2단, 3단 등으로 Grid 형태로 데이터를 처리할 수 있다.
LinearLayoutManager의 orientation(방향) 속성을 사용해 리사이클러뷰를 수평으로 할지, 수직으로 할지 결정할 수 있다. HORIZONTAL로 설정하면 수평으로 리사이클러뷰가 만들어진다. VERTICAL로 설정하면 수직으로 리사이클러뷰가 만들어진다.
두 가지의 LinearLayoutManger를 설정하고 각각 뷰가 만들어지는 레이아웃에 바인딩해준다. 레이아웃 이름은 각각 recycler_horizontal, recycler_vertical로 지을 것이다. 아직 레이아웃에 해당 뷰들을 생성하지 않았기 때문에 코드 상에서는 오류가 발생할 것이다.
코틀린의 apply 함수를 사용하면 선언한 객체 이름을 생략하고 접근할 수 있기 때문(참고)에 apply 함수를 사용하였다.
activity layout
리사이클러뷰가 들어갈 레이아웃이다.
<?xml version="1.0" encoding="utf-8"?>
<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>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RecyclerView"
android:textSize="27sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LinearLayoutManager"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_horizontal"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:background="@color/lightgray"
android:orientation="horizontal"
android:scrollbars="horizontal"
app:layout_constraintBottom_toBottomOf="@id/recycler_vertical"
app:layout_constraintEnd_toStartOf="@id/recycler_vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/recycler_vertical"
app:layout_constraintVertical_bias="0.545" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_vertical"
android:layout_width="wrap_content"
android:layout_height="150dp"
android:layout_marginTop="16dp"
android:background="@color/lightgray"
android:orientation="vertical"
android:scrollbars="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/recycler_horizontal"
app:layout_constraintTop_toBottomOf="@id/title2" />
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
layout 안 ConstraintLayout 안에 RecyclerView를 생성하였다.
각각 id 값을 recycler_horizontal, recycler_vertical로 설정해주었기 때문에 MainActivity에서 자동으로 바인딩되어 데이터가 들어가게 된다.
리사이클러뷰를 구분하기 위하여 background 값을 lightgray로 지정하였다. lightgray는 기본적으로 있는 색상이 아니기 때문에 res > values > colors.xml 폴더 내에 아래와 같이 lightgray 색상을 추가해주었다.
<color name="lightgray">#EEEEEE</color>