경험의 기록

리사이클러뷰에서 데이터바인딩과 라이브데이터를 사용하여

리스트의 변화를 감지하여 바로 리사이클러뷰를 갱신해주려고 한다.

 

 

[2022.04.20 추가]

2022.04.16 - [Android/AAC, MVVM] - [Android] 리사이클러뷰 BindingAdapter, ListAdapter 사용하여 데이터바인딩 하기

 

[Android] 리사이클러뷰 BindingAdapter, ListAdapter 사용하여 데이터바인딩 하기

2021.05.30 - [Android/AAC, MVVM] - [Android] 리사이클러뷰에서 DataBinding, LiveData 사용하기 (+ BindingAdapter) [Android] 리사이클러뷰에서 DataBinding, LiveData 사용하기 (+ BindingAdapter) 리사이클..

hanyeop.tistory.com

위 링크 글에서 더 최신 방법으로 다루었습니다. 위 글을 먼저 보시고 이해가지 않는 부분이 있다면

현재글을 참고해주세요 😀

 


1️⃣ 데이터 바인딩

종속성 추가

 buildFeatures {
        dataBinding true
    }
 def lifecycle_version = "2.3.0"

    // ViewModel - 라이프 사이클
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    // LiveData - 데이터의 변경 사항을 알 수 있음
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

데이터바인딩과

뷰모델, 라이브데이터를 추가해준다.

 

2021.05.17 - [안드로이드/기본] - [Android] 자주쓰는 RecyclerView 사용하기 (+ ViewBinding)

 

[Android] 자주쓰는 RecyclerView 사용하기 (+ ViewBinding)

RecyclerView 는 안드로이드 개발을 할 때 자주 사용하게 된다. 그런데 만들때마다 조금씩 헷갈릴 때가 있어서, 하나하나 깔끔하게 과정을 정리하여 참고할 수 있는 라이브러리처럼 만들어 보고자

hanyeop.tistory.com

2021.04.26 - [안드로이드/AAC, MVVM] - [Android] 안드로이드 ViewModel, LiveData (+DataBinding)

 

[Android] 안드로이드 ViewModel, LiveData (+DataBinding)

2021.04.19 - [안드로이드/기본] - [Android] 안드로이드 AAC & MVVM [Android] 안드로이드 AAC & MVVM 액티비티, 프래그먼트에 너무 많은 코드를 넣게 되면 점점 무거워져 다루기 힘들어지게 된다. 앱이 카메라.

hanyeop.tistory.com

뷰모델과 라이브데이터, 리사이클러뷰는 이 글에서 다뤘으므로 간략히 적으려고 한다.

 

데이터클래스

data class User(
    val name: String,
    val age: Int
)

리사이클러뷰의 아이템으로 사용할 데이터클래스를 만들어준다.

 

아이템뷰

이런식으로 아이템뷰를 만들려고 하므로

<resources>
    <string name="user_format">이름은 %s 나이는 %d</string>
</resources> 

String Format를 만들어주고

 

<?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>
        <variable
            name="user"
            type="com.hanyeop.recyclerviewdatabindingex.User"/>


    </data>

    <LinearLayout
        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="@{user.name}"
                    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="@{Integer.toString(user.age)}"
                    android:textSize="16sp"
                    app:layout_constraintStart_toStartOf="@+id/name_text"
                    app:layout_constraintTop_toBottomOf="@+id/name_text" />

                <TextView
                    android:id="@+id/intro_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="20dp"
                    android:text="@{@string/user_format(user.name, user.age)}"
                    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>
</layout> 

아이템뷰의 텍스트를 데이터클래스와 바인딩해서 작성해준다.

 

리사이클러뷰 어댑터

class MyAdapter()
    : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    var userList = mutableListOf<User>()

    // 생성된 뷰 홀더에 값 지정
    class MyViewHolder(val binding: MainItemBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(currentUser : User) {
            binding.user = currentUser
        }
    }

    // 어떤 xml 으로 뷰 홀더를 생성할지 지정
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val binding = MainItemBinding.inflate(LayoutInflater.from(parent.context),parent,false)
        return MyViewHolder(binding)
    }

    // 뷰 홀더에 데이터 바인딩
    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bind(userList[position])
    }

    // 뷰 홀더의 개수 리턴
    override fun getItemCount(): Int {
        return userList.size
    }
} 

리사이클러뷰 어댑터를 작성해주고

 

메인액티비티

class MainActivity : AppCompatActivity() {

    private lateinit var binding : ActivityMainBinding
//    private lateinit var mainViewModel: MainViewModel
    private lateinit var myAdapter: MyAdapter
    private val TAG = "test5"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        binding = DataBindingUtil.setContentView(this,R.layout.activity_main)
//        mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        myAdapter = MyAdapter()
        binding.recyclerView.adapter = myAdapter

        myAdapter.userList = mutableListOf(
            User("Han",25),
            User("Lee",33)
        )

        binding.button.setOnClickListener {
            myAdapter.userList.add(User("test",50))
            Log.d(TAG, "onCreate: 실행")
        }
    }
} 

메인에서 액티비티를 바인딩해주고,

어댑터를 연결하여준다.

버튼을 클릭하면 어댑터의 리스트에 임의의 값을 추가하도록 하였다.

 

화면에 각각 바인딩된 값이 잘 출력되는 것을 확인할 수 있다.

❗ 하지만 리스트에 추가한 정보는 어댑터에서 감지하지 못하기 때문에 갱신해주지 못한다. 따라서 뷰모델, 라이브데이터를 사용하여 옵저버패턴을 구현해야 한다.

 

2️⃣ 라이브 데이터, 뷰모델 사용하기

뷰모델

class MainViewModel : ViewModel() {
    private val _userList = MutableLiveData<ArrayList<User>>()
    val userList : LiveData<ArrayList<User>>
        get() = _userList

    private var items = ArrayList<User>()

    init{
        items = arrayListOf(
                User("Han",25),
                User("Lee",33)
        )
        _userList.value = items
    }

    fun buttonClick(){
        val user = User("Test",20)
        items.add(user)
        _userList.value = items
    }
} 

userList를 가지는 뷰모델을 작성해준다.

여기서 아이템을 초기화해주며, 아이템 추가 기능도 이 곳으로 옮겼다.

 

메인xml

<data>

        <variable
            name="viewModel"
            type="com.hanyeop.recyclerviewdatabindingex.MainViewModel" />

    </data>

데이터바인딩하여

뷰모델을 변수 추가 해주고,

 

            android:onClick="@{() -> viewModel.buttonClick()}"

버튼에 뷰모델의 buttonClick을 바인딩해준다.

 

리사이클러뷰 어댑터

fun setData(data : ArrayList<User>){
        userList = data
        notifyDataSetChanged()
    }

받아온 데이터 값을

유저리스트에 할당하여

뷰를 다시 갱신시키는 함수를 작성해주고

 

메인액티비티

// 뷰모델 연결
        mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        binding.viewModel = mainViewModel

        mainViewModel.userList.observe(this, Observer {
            myAdapter.setData(it)
        })

만든 뷰모델을 연결해주고

옵저버를 만들어주어 리스트가 변경될 때 감지하여 setData를 호출해준다.

 

버튼을 클릭할 때 마다 잘 생성된다.

 

 

3️⃣ 바인딩 어댑터 사용하기

마지막으로, 바인딩 어댑터를 사용하면 setData 함수 호출 없이 리스트가 변동될 때 뷰를 갱신해 줄 수 있다.

 

plugins {
    id 'kotlin-kapt'
}

어노테이션 사용을 위해 추가해준다.

 

바인딩 어댑터 생성

object MyBindingAdapter{

    @BindingAdapter("items")
    @JvmStatic
    fun setItems(recyclerView: RecyclerView, items : ArrayList<User>){

        if(recyclerView.adapter == null)
            recyclerView.adapter = MyAdapter()

        val myAdapter = recyclerView.adapter as MyAdapter

        myAdapter.userList = items
        myAdapter.notifyDataSetChanged()
    }

}

오브젝트로 바인딩 어댑터를 생성해주고

@BindingAdapter 어노테이션으로 함수를 작성해주면

커스텀한 속성을 xml에서 사용할 수 있다.

또한 @JvmStatic 어노테이션을 작성하여 스태틱으로 생성해준다.

여기서 어댑터도 연결해준다.

 

리사이클러뷰의 아이템을 설정하기 위한 속성을 정의하였다.

 

 

메인xml

<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintBottom_toTopOf="@+id/button"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:items="@{viewModel.userList}"/>

새로만든 items 속성을 사용하여

뷰모델의 유저리스트를 연결해준다.

 

메인액티비티

class MainActivity : AppCompatActivity() {

    private lateinit var binding : ActivityMainBinding
    private lateinit var mainViewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this,R.layout.activity_main)

        // 뷰모델 연결
        mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        binding.viewModel = mainViewModel

        // 뷰모델을 LifeCycle 에 종속시킴, LifeCycle 동안 옵저버 역할을 함
        binding.lifecycleOwner = this
    }
}

바인딩어댑터에서 어댑터를 연결했으므로 어댑터 연결하는 코드도 필요없고,

뷰모델을 연결하여 lifecycleOwner를 액티비티에 종속시키면,

옵저버 역할을 하여 라이브데이터와 동일한 값으로 뷰가 자동으로 갱신된다.

 

데이터 바인딩을 사용하여 코드가 굉장히 깔끔해진것을 확인할 수 있다.

 

 

 

허나 아이템을 바인딩하게되면

뷰가 notifyDataSetChanged 를 호출할때마다

깜빡이는 현상이 존재하게 되는데

 

object MyBindingAdapter{

    @BindingAdapter("items")
    @JvmStatic
    fun setItems(recyclerView: RecyclerView, items : ArrayList<User>){

        if(recyclerView.adapter == null) {
            val adapter = MyAdapter()
            adapter.setHasStableIds(true)
            recyclerView.adapter = adapter
        }

        val myAdapter = recyclerView.adapter as MyAdapter

        myAdapter.userList = items
        myAdapter.notifyDataSetChanged()
    }

}

어댑터를 생성할때 setHasStableIds를 true로 해주고

override fun getItemId(position: Int): Long {
        return position.toLong()
    }

어댑터에서 getItemId를 오버라이드 해주면

 

깜빡이는 현상이 사라진다.

 

 

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

 

HanYeop/AndroidStudio-Practice2

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

github.com

 

 

 

참고

https://kangmin1012.tistory.com/16?category=879935 

https://gogorchg.tistory.com/entry/AndroidDataBinding-String-Format-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0

https://salix97.tistory.com/263

 

 

 

 

 

 

 

 

 

반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band
loading