반응형

ConcatAdapter란 하나의 리사이클러뷰에 여러 가지 어답터를 결합? 하여 순차적으로 나타낼 수 있도록 도와주는 어답터입니다. 처음 나왔을 땐 MergeAdapter로 불렸지만 현재는 ConcatAdapter라고 합니다.

위의 움짤은 3개의 어답터를 ConcatAdapter를 사용하여 하나의 리사이클러뷰에 연결시켜준 모습입니다. 

첫 번째, 두 번째, 세 번째 어답터를 순차적으로 넣어주었기 때문에 첫 번째, 두 번째, 세 번째 리사이클러뷰로 순차적으로 나타나는 것을 볼 수 있습니다.

val concatAdapter = ConcatAdapter(firstAdp, secondAdp, thirdAdp)

의 결과

아래와 같이 두번째, 세 번째, 첫 번째 어답터 순으로 넣어주었다면 두 번째, 세 번째, 첫 번째 리사이클러뷰 순으로 보이게 될 것입니다.

val concatAdapter = ConcatAdapter(secondAdp, thirdAdp, firstAdp)

의 결과

의존성 추가

ConcatAdapter을 사용하기위해서는 dependency를 추가해주어야 합니다.

implementation "androidx.recyclerview:recyclerview:1.2.0-rc01"

모듈의 그레들에 위의 의존성을 추가해주면 됩니다.

간단한 레이아웃 구성(XML)

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/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="1dp"
        android:layout_marginTop="1dp"
        android:layout_marginEnd="1dp"
        android:layout_marginBottom="50dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/recyclerView"
        app:layout_constraintBottom_toBottomOf="parent"
        android:weightSum="9">
        <Button
            android:id="@+id/btnAddFirst"
            android:text="첫번째에 추가"
            android:layout_weight="3"
            android:layout_margin="3dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <Button
            android:id="@+id/btnAddSecond"
            android:text="두번째에 추가"
            android:layout_weight="3"
            android:layout_margin="3dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/btnAddThird"
            android:text="세번째에 추가"
            android:layout_weight="3"
            android:layout_margin="3dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

first_items.xml

리사이클러뷰에 사용될 아이템들의 레이아웃을 구성해주었습니다.

첫 번째 리사이클러뷰 아이템의 레이아웃의 소스코드는 아래와 같습니다.

xml 파일의 이름은 first_items.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"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@color/first_color">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        android:text="첫번째 리사이클러뷰"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        android:layout_marginStart="20dp"
        android:layout_marginTop="20dp"
        android:text="텍스트제목"
        app:layout_constraintStart_toEndOf="@+id/title"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

미리보기상 화면으로는 아래와 같습니다.

두 번째, 세 번째 소스코드도 위와 동일합니다.

second_items.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"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@color/second_color">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        android:text="두번째 리사이클러뷰"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        android:layout_marginStart="20dp"
        android:layout_marginTop="20dp"
        android:text="텍스트제목"
        app:layout_constraintStart_toEndOf="@+id/title"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

third_items.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"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@color/second_color">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        android:text="두번째 리사이클러뷰"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        android:layout_marginStart="20dp"
        android:layout_marginTop="20dp"
        android:text="텍스트제목"
        app:layout_constraintStart_toEndOf="@+id/title"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

RecyclerVIew를 위한 Adapter 만들기

리사이클러뷰를 위한 어답터를 만들어주도록 하겠습니다.

첫 번째, 두번째, 세번째 어답터가 모두 동일하기 때문에 첫번째 어답터로만 설명하겠습니다.

첫번째 어답터인 FirstAdapter의 전체 소스코드는 아래와 같습니다.

package com.example.concatadapter.Adapters

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.concatadapter.databinding.FirstItemsBinding

class FirstAdapter(private var itemList: MutableList<String> = mutableListOf()) :
    RecyclerView.Adapter<FirstHolder>() {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FirstHolder {
        val binding = FirstItemsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return FirstHolder(binding)
    }

    override fun onBindViewHolder(holder: FirstHolder, position: Int) {
        holder.setItem(itemList[position])
    }

    override fun getItemCount(): Int {
        return itemList.size
    }

    fun addItem(item: String) {
        this.itemList.add(item)

        notifyItemInserted(itemList.size - 1)
    }

}

class FirstHolder(val binding: FirstItemsBinding) : RecyclerView.ViewHolder(binding.root) {

    fun setItem(item: String) {
        binding.item.text = item
    }

}

FirstHolder

FirstAdapter에 뷰 홀더로 쓰일 FirstHolder를 만들어 주었습니다.

class FirstHolder(val binding: FirstItemsBinding) : RecyclerView.ViewHolder(binding.root) {

    fun setItem(item: String) {
        binding.item.text = item
    }

}

first_item 레이아웃을  바인딩하고 setItem 함수를 만들었습니다. FirstAdapter의 onBindViewHolder에서 setItem 함수를 호출하여레이아웃에 아이템을 나타내게 됩니다.

FirstAdapter

class FirstAdapter(private var itemList: MutableList<String> = mutableListOf()) :
    RecyclerView.Adapter<FirstHolder>() {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FirstHolder {
        val binding = FirstItemsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return FirstHolder(binding)
    }

    override fun onBindViewHolder(holder: FirstHolder, position: Int) {
        holder.setItem(itemList[position])
    }

    override fun getItemCount(): Int {
        return itemList.size
    }

    fun addItem(item: String) {
        this.itemList.add(item)
        notifyItemInserted(itemList.size - 1)
    }

}

FirstAdapter는 위와 같이 구성되어 있습니다. 생성자로 itemList를 받아올 수 있도록 하였습니다. 

Adapter 내부에 addItem 함수를 만들어서 메인엑티비티에서 버튼이 눌렸을 때 addItem을 호출하여 아이템을 추가할 수 있도록 만들었습니다. itemList에 아이템이 추가된 후 어답터에게 변화가 있음을 알리는 notifyItemInserted 함수를 사용하였습니다. 

position은 0부터 시작하기 때문에 itemList.size -1을 통하여 방금 추가된 아이템의 위치(position)를 넘겨주었습니다.

 

SecondAdapter, ThirdAdapter 또한 위와 동일하게 구성되어있습니다.

SecondAdapter

class SecondAdapter(private var itemList: MutableList<String> = mutableListOf()) :
    RecyclerView.Adapter<SecondHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SecondHolder {
        val binding =
            SecondItemsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return SecondHolder(binding)
    }

    override fun onBindViewHolder(holder: SecondHolder, position: Int) {
        holder.setItem(itemList[position])
    }

    override fun getItemCount(): Int {
        return itemList.size
    }

    fun addItem(item: String) {
        itemList.add(item)
        notifyItemInserted(itemList.size - 1)
    }
}

class SecondHolder(val binding: SecondItemsBinding) : RecyclerView.ViewHolder(binding.root) {

    fun setItem(item: String) {
        binding.item.text = item
    }
}

ThirdAdapter

class ThirdAdapter(private var itemList: MutableList<String> = mutableListOf()) :
    RecyclerView.Adapter<ThirdHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ThirdHolder {
        val binding =
            ThirdItemsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ThirdHolder(binding)
    }

    override fun onBindViewHolder(holder: ThirdHolder, position: Int) {
        holder.setItem(itemList[position])
    }

    override fun getItemCount(): Int {
        return itemList.size
    }

    fun addItem(item: String) {
        itemList.add(item)
        notifyItemInserted(itemList.size - 1)
    }
}

class ThirdHolder(val binding: ThirdItemsBinding) : RecyclerView.ViewHolder(binding.root) {

    fun setItem(item: String) {
        binding.item.text = item
    }
}

MainActivity 구성

메인엑티비티의 전체적인 소스코드는 아래와 같습니다.

class MainActivity : AppCompatActivity() {

    val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    val firstItemList = mutableListOf<String>()
    val secondItemList = mutableListOf<String>()
    val thirdItemList = mutableListOf<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        getFiveItems(firstItemList)
        getFiveItems(secondItemList)
        getFiveItems(thirdItemList)

        val firstAdp = FirstAdapter(firstItemList)
        val secondAdp = SecondAdapter(secondItemList)
        val thirdAdp = ThirdAdapter(thirdItemList)

        val concatAdapter = ConcatAdapter(firstAdp, secondAdp, thirdAdp)

        binding.recyclerView.adapter = concatAdapter
        binding.recyclerView.layoutManager = LinearLayoutManager(this)

        // 추가 버튼이 눌렸을 때 판정들
        with(binding) {
            btnAddFirst.setOnClickListener {
                firstItemList.add("${firstItemList.size + 1}번째 아이템")
                firstAdp.notifyItemInserted(firstItemList.size - 1)
            }

            btnAddSecond.setOnClickListener {
                secondItemList.add("${secondItemList.size + 1}번째 아이템")
                secondAdp.notifyItemInserted(secondItemList.size - 1)
            }

            btnAddThird.setOnClickListener {
                thirdItemList.add("${thirdItemList.size + 1}번째 아이템")
                thirdAdp.notifyItemInserted(thirdItemList.size - 1)
            }
        }
    }

    fun getFiveItems(itemList: MutableList<String>) {

        for (no in 1..5) {
            itemList.add("${no}번째 아이템")
        }

    }
}

getFiveItems 함수를 만들고 oncreate에서 호출하여 firstItemList, secondItemList, thirdItemList에 각각 5개의 아이템이 담기도록 구성했습니다.

fun getFiveItems(itemList: MutableList<String>) {

        for (no in 1..5) {
            itemList.add("${no}번째 아이템")
        }

    }
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(binding.root)

    getFiveItems(firstItemList)
    getFiveItems(secondItemList)
    getFiveItems(thirdItemList)
    
    //..이하 생략
}

그 후 First, Second, Third 어답터를 순차적으로 생성해주고 ConcatAdapter를 사용하여 연결해주었습니다.

리사이클러뷰의 adapter에 concatAdapter를 사용하여주었습니다.

val firstAdp = FirstAdapter(firstItemList)
val secondAdp = SecondAdapter(secondItemList)
val thirdAdp = ThirdAdapter(thirdItemList)

val concatAdapter = ConcatAdapter(firstAdp, secondAdp, thirdAdp)

binding.recyclerView.adapter = concatAdapter
binding.recyclerView.layoutManager = LinearLayoutManager(this)

마지막으로 첫 번째에 추가 / 두 번째에 추가 / 세 번째에 추가 / 버튼이 눌렸을 때 각각 해당하는 어답터에 아이템을 추가할 수 있도록 구성하였습니다.

// 추가 버튼이 눌렸을 때 판정들
with(binding) {
    // 첫번째에 추가
    btnAddFirst.setOnClickListener {
        firstItemList.add("${firstItemList.size + 1}번째 아이템")
        firstAdp.notifyItemInserted(firstItemList.size - 1)
    }
    // 두번째에 추가
    btnAddSecond.setOnClickListener {
        secondItemList.add("${secondItemList.size + 1}번째 아이템")
        secondAdp.notifyItemInserted(secondItemList.size - 1)
    }
    // 세번째에 추가
    btnAddThird.setOnClickListener {
        thirdItemList.add("${thirdItemList.size + 1}번째 아이템")
        thirdAdp.notifyItemInserted(thirdItemList.size - 1)
    }
}

동작하는 모습

세 개의 어답터가 순차적으로 나타나고 아이템이 추가되는 것도 잘 동작하는 것을 볼 수 있습니다!

예제 소스 코드

위에 나와있는 예제의 전체 코드입니다!

https://github.com/soopeach/ConcatAdapter

 

GitHub - soopeach/ConcatAdapter

Contribute to soopeach/ConcatAdapter development by creating an account on GitHub.

github.com

 

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기