경험의 기록

하나의 리사이클러뷰에서 위의 카카오톡 대화창과 같이 여러 뷰타입을 사용할 수 있다.

 

2021.08.03 - [안드로이드/기본] - [Android] 리사이클러뷰 갱신 효율성을 위한 ListAdapter 사용하기

 

[Android] 리사이클러뷰 갱신 효율성을 위한 ListAdapter 사용하기

일반적으로 리사이클러뷰에선 아이템이 갱신될 때 마다 뷰가 갱신되기 때문에 아이템이 많을수록 효율성이 떨어지게 된다. 상식적으로 바뀐 뷰만 변경하는 것이 더 효율적이므로 안드로이드에

hanyeop.tistory.com

위의 코드를 기반으로 작성하였으며

리사이클러뷰 어댑터 작성 과정은 생략하였다.


레이아웃 만들기

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="wrap_content"
    android:orientation="vertical">

    <androidx.cardview.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <ImageView
                android:id="@+id/imageView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/ic_launcher_foreground" />

            <TextView
                android:id="@+id/name_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="10dp"
                android:layout_marginTop="16dp"
                android:text="TextView"
                android:textSize="30sp"
                android:textStyle="bold"
                app:layout_constraintStart_toEndOf="@+id/imageView"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/description_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="TextView"
                android:textSize="16sp"
                app:layout_constraintStart_toStartOf="@+id/name_text"
                app:layout_constraintTop_toBottomOf="@+id/name_text" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>
</LinearLayout>

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="wrap_content"
    android:orientation="vertical">

    <androidx.cardview.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <ImageView
                android:id="@+id/imageView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/ic_launcher_foreground" />

            <TextView
                android:id="@+id/name_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="TextView"
                android:textSize="30sp"
                android:textStyle="bold"
                app:layout_constraintEnd_toStartOf="@+id/imageView"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/description_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="TextView"
                android:textSize="16sp"
                app:layout_constraintEnd_toStartOf="@+id/imageView"
                app:layout_constraintTop_toBottomOf="@+id/name_text" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>
</LinearLayout>

 

먼저 사용할 뷰타입 레이아웃들을 만들어준다.

왼쪽 정렬과 오른쪽 정렬된 두가지의 레이아웃을 만들어 주었다.

 

타입 정의

class Constants {

    companion object{
        const val LEFT_POSITION = 0
        const val RIGHT_POSITION = 1
    }
}

타입을 구분하기 위한 상수를 정의해주고

 

@Entity(tableName = "item_table")
data class Item(
    // autoGenerate = true , 자동으로 PrimaryKey 생성해줌
    @PrimaryKey(autoGenerate = true)
    val id : Int,
    val name : String,
    val description : String,
    val type : Int
)

사용할 데이터에서 타입을 구분하기 위한

type 파라미터를 만들어준다.

 

어댑터 구현하기

class MyAdapter : ListAdapter<Item, RecyclerView.ViewHolder>(diffUtil) {

    // 왼쪽 뷰홀더
    inner class LeftViewHolder(private val binding : LayoutItemBinding) : RecyclerView.ViewHolder(binding.root){
        fun bind(item : Item){
            binding.apply {
                nameText.text = item.name
                descriptionText.text = item.description
            }
        }
    }

    // 오른쪽 뷰홀더
    inner class RightViewHolder(private val binding : LayoutItemRightBinding) : RecyclerView.ViewHolder(binding.root){
        fun bind(item : Item){
            binding.apply {
                nameText.text = item.name
                descriptionText.text = item.description
            }
        }
    }

    // 타입에 따라 다른 뷰홀더 생성
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        // viewType = getItemViewType 의 리턴 값
        return when(viewType){
            LEFT_POSITION ->{
                val binding = LayoutItemBinding.inflate(LayoutInflater.from(parent.context),parent,false)
                LeftViewHolder(binding)
            }
            RIGHT_POSITION ->{
                val binding = LayoutItemRightBinding.inflate(LayoutInflater.from(parent.context),parent,false)
                RightViewHolder(binding)
            }
            else -> throw RuntimeException("Error")
        }
    }

    // 타입에 따라 바인딩
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val currentItem = getItem(position)

        when(currentItem.type){
            LEFT_POSITION -> (holder as LeftViewHolder).bind(currentItem)
            RIGHT_POSITION -> (holder as RightViewHolder).bind(currentItem)
        }
    }

    // 리스트 갱신
    override fun submitList(list: List<Item>?) {
        super.submitList(list)
    }

    // 아이템 타입 리턴
    override fun getItemViewType(position: Int): Int {
        return getItem(position).type
    }

    // diffUtil 추가
    companion object{
        val diffUtil = object : DiffUtil.ItemCallback<Item>(){
            override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
                return oldItem.id == newItem.id
            }

            override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
                return oldItem.hashCode() == newItem.hashCode()
            }
        }
    }
}

전체 코드이다.

 

하나씩 설명하자면

getItemViewType을 오버라이드하여 현재아이템의 뷰타입 리턴값을

아이템의 type 값으로 설정해준다.

 

 

이제 리턴받은 viewType에 따라 다른 뷰홀더를 생성해준다.

뷰바인딩이 사용되었다.

 

이제 타입에 맞는 바인딩을 호출해준다.

 

타입에 따라 각각 데이터를 바인딩해준다.

 

메인액티비티

예시로 임의의 값을 왼쪽이나 오른쪽에 추가할 수 있도록 하였다.

 

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var adapter: MyAdapter
    private val viewModel by viewModels<ItemViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 뷰 바인딩
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 어댑터 연결
        adapter = MyAdapter()

        // 아이템 변경시 갱신
        viewModel.readAllData.observe(this){
            adapter.submitList(it)
        }

        binding.apply {
            recyclerView.adapter = adapter
            recyclerView.layoutManager = LinearLayoutManager(this@MainActivity)

            // 왼쪽에 추가
            leftButton.setOnClickListener {
                val item = Item(0,nameText.text.toString(),descriptionText.text.toString(),LEFT_POSITION)
                viewModel.addItem(item)
            }

            // 오른쪽에 추가
            rightButton.setOnClickListener {
                val item = Item(0,nameText.text.toString(),descriptionText.text.toString(), RIGHT_POSITION)
                viewModel.addItem(item)
            }

            deleteButton.setOnClickListener{
                viewModel.deleteAll()
            }
        }

    }
}

왼쪽, 오른쪽 버튼 클릭 시 각각 type에 맞는 값을 추가하였다.

 

 

 

잘 구현된 것을 확인할 수 있다.

 

 

 

https://github.com/HanYeop/AndroidStudio-Practice2/tree/master/RecyclerViewEx2

 

GitHub - HanYeop/AndroidStudio-Practice2: (2021.05.20~) 안드로이드 학습 내용 저장소

(2021.05.20~) 안드로이드 학습 내용 저장소. Contribute to HanYeop/AndroidStudio-Practice2 development by creating an account on GitHub.

github.com

 

 

 

참고

https://huiveloper.tistory.com/21

 

 

 

반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band
loading