[Android/Kotlin] 안드로이드 Multi Selection RecyclerView 만들기
안녕하세요. 오늘은 RecyclerView로 여러 아이템을 선택하는 방법에 대해서 알아보겠습니다.
흔히 Multi Selection RecyclerView라고 말합니다.
리스트뷰에서 특정 아이템을 선택하여 따로 저장하거나 삭제하는 경우에 유용하게 쓸 수 있습니다.
오늘 보여드릴 시나리오는 아이템 목록에서 다중선택을 통하여 삭제하는 흐름 입니다.
처음 아이템이 선택되면 버튼이 활성화 되고 삭제하기 버튼을 클릭하면 선택 된 아이템의 갯수가 토스트 메시지로 표시가 됩니다.
완성된 모습은 아래와 같습니다.
STEP01. 레이아웃 구성하기
레이아웃은 RecyclerView와 그 아래 삭제 버튼으로 구성했습니다.
[activity_main.xml]
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_expense"
android:layout_width="match_parent"
android:layout_height="0dp"
android:overScrollMode="never"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/btn_delete"
/>
<Button
android:id="@+id/btn_delete"
android:text="삭제하기"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
STEP02. RecyclerView 구현하기
RecyclerView에서 보여줄 데이터 클래스 Expense를 만들어 줍니다.
[Expense.kt]
data class Expense(
var title: String,
var amount: Int
)
RecyclerVIew에서 보여줄 레이아웃입니다.
[item_expense.xml]
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:id="@+id/txt_title"
android:padding="16dp"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/txt_amount"
app:layout_constraintBottom_toBottomOf="parent"
tools:text="점심 식사"
/>
<TextView
android:id="@+id/txt_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:text="8000"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Adapter 클래스를 아래와 같이 만들어줍니다.
Main Activity에서 클릭 이벤트를 처리할 수 있게 setOnItemLickListener() 함수를 만들었습니다.
[ExpenseAdapter.kt]
class ExpenseAdapter : ListAdapter<Expense, ExpenseAdapter.MyViewHolder>(DiffUtils()) {
inner class MyViewHolder(
private val binding: ItemExpenseBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(expense: Expense) {
with(binding) {
txtTitle.text = expense.title
txtAmount.text = expense.amount.toString()
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
MyViewHolder(ItemExpenseBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(getItem(position))
}
private class DiffUtils : DiffUtil.ItemCallback<Expense>() {
override fun areItemsTheSame(oldItem: Expense, newItem: Expense): Boolean {
return oldItem.title == newItem.title
}
override fun areContentsTheSame(oldItem: Expense, newItem: Expense): Boolean {
return oldItem == newItem
}
}
private var onItemClickListener: ((Expense) -> Unit)? = null
fun setOnItemClickListener(listener: (Expense) -> Unit) {
onItemClickListener = listener
}
}
액티비로 돌아가서 Fake Data를 만들어 RecyclerView 어댑터에 전달하는 코드를 작성했습니다.
[MainActivity.kt]
class MainActivity : AppCompatActivity() {
private var _binding: ActivityMainBinding? = null
private val binding get() = _binding!!
private lateinit var expenseAdapter: ExpenseAdapter
private lateinit var dataList: ArrayList<Expense>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
loadFakeData()
binding.btnDelete.isEnabled = false
binding.recyclerExpense.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(this@MainActivity)
expenseAdapter = ExpenseAdapter()
adapter = expenseAdapter
}
expenseAdapter.submitList(dataList)
}
private fun loadFakeData() {
dataList = ArrayList()
dataList.clear()
val default = 1000
for (i in 1..20) {
val expense = Expense(title = "밥먹기 $i", amount = default * i)
dataList.add(expense)
}
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
여기까지 작성하시면 RecyclerView 나타납니다.
이제 RecyclerView에 선택할 수 있도록 코드를 수정해 보도록 하겠습니다.
선택된 expense를 담을 리스트를 하나 생성해 줍니다. RecyclerView 레이아웃 클릭 시 applySelection() 함수를 호출하여 selectedExpense 리스트에 담겨 있는지 여부에 따라서 추가와 삭제를 해줍니다.
추가적으로 changeBackground() 함수를 호출하여 UI를 변경해 줍니다.
[ExpenseAdapter.kt]
private var selectedExpense = arrayListOf<Expense>()
...
// bind 함수 수정
binding.root.setOnClickListener {
applySelection(binding, expense)
onItemClickListener?.let { it(expense) }
}
...
private fun applySelection(binding: ItemExpenseBinding, expense: Expense) {
if (selectedExpense.contains(expense)) {
selectedExpense.remove(expense)
changeBackground(binding, R.color.white)
} else {
selectedExpense.add(expense)
changeBackground(binding, R.color.purple_200)
}
}
private fun changeBackground(binding: ItemExpenseBinding, resId: Int) {
binding.layoutContainer.setBackgroundColor(ContextCompat.getColor(binding.root.context, resId))
}
fun getSelectedExpense() = selectedExpense.size
메인 액티비로 돌아가서 아이템이 클릭될 때 마다 Adpater 클래스의 getSelectedExpense() 함수를 호출하여,
아이템 여부에 따라서 버튼의 선택 가능 여부를 변경해줍니다.
[MainActivity.kt]
expenseAdapter.setOnItemClickListener { response ->
binding.btnDelete.isEnabled = expenseAdapter.getSelectedExpense() > 0
}
binding.btnDelete.setOnClickListener {
Toast.makeText(this, "삭제: ${expenseAdapter.getSelectedExpense()}개",
Toast.LENGTH_SHORT).show()
}
'기리's Android 이야기' 카테고리의 다른 글
[Android/Kotlin] 안드로이드 FLAG_FULLSCREEN Deprecated... (0) | 2021.02.04 |
---|---|
[Android/Error] 안드로이드 NullPointerException: WindowInsetsController... (1) | 2021.02.04 |
[Android/Kotlin] 안드로이드 TextInputLayout 사용해보기 (0) | 2021.02.02 |
[Android/Kotlin] 안드로이드 Room Database 사용하기(2) Select... (1) | 2021.02.01 |
[Android/Error] 안드로이드 ViewModelProvider 에러발생 (0) | 2021.01.31 |
댓글