Development record of developer who study hard everyday.

레이블이 InverseBindingAdapter인 게시물을 표시합니다. 모든 게시물 표시
레이블이 InverseBindingAdapter인 게시물을 표시합니다. 모든 게시물 표시
, , , ,

안드로이드 커스텀뷰(CustomView) 양방향 데이터바인딩(DataBinding)

 안드로이드 커스텀뷰 양방향 데이터바인딩

안드로이드 블로그

첫 회사에 일하게되면서 데이터바인딩(DataBinding)을 처음 알게되었다.

안드로이드 문서를 열심히 읽고 CodeLab도 따라해보니 이제는 익숙해졌다.

하지만 회사 전임자가 작성한 코드를 보면 커스텀뷰를 만들어서 데이터바인딩을 사용하는 경우를 본 적이 있다.

회사에 입사한지 1년 3개월이 지나서야 CustomView를 사용했을 때 데이터바인딩을 하는 방법을 공부했다. ㅎㅎ

그럼 같이 따라해보길 바란다,

1. Dependency 추가

프로젝트 수준 build.gradle에 kotln-kapt를 추가한다.

그래야지 데이터바인딩에 사용하는 어노테이션을 에러 없이 사용가능하다,

plugins {
id 'com.android.application' version '7.2.1' apply false
id 'com.android.library' version '7.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
id "org.jetbrains.kotlin.kapt" version "1.6.10" apply false    //요놈 추가

}


앱 수준 build.gradle에도 kotlin-kapt를 추가한다

plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'    //요놈 추가
}

android {
...

buildFeatures {    //이놈들 추가
dataBinding true
}
}

dependencies {

implementation 'androidx.activity:activity-ktx:1.6.0'    //뷰모델 만들 때 필요

}


2. CustomView xml(레이아웃) 만들기

<?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">

<data>

</data>

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

<TextView
android:id="@+id/emojiTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />

<EditText
android:id="@+id/mainEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
app:layout_constraintStart_toEndOf="@id/emojiTextView"
app:layout_constraintTop_toTopOf="@id/emojiTextView"
app:layout_constraintBottom_toBottomOf="@id/emojiTextView"
app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>


3. CustomView 클래스 만들기

class CustomEditText(context: Context, attributeSet: AttributeSet? = null) : FrameLayout(context, attributeSet) {
private lateinit var binding : CustomEdittextBinding
val editText    //1
get() = binding.mainEditText
init {
binding = CustomEdittextBinding.inflate(LayoutInflater.from(context), this, true)
binding.emojiTextView.text = "\uD83D\uDE00"
}

}

1) 커스텀뷰인 CustomEditText에 양방향 데이터바인딩 기능을 사용해야하기 때문에 editText를 참조한다.


4. 뷰모델 만들기

class MainViewModel : ViewModel() {
val content = MutableLiveData("")    //1
val TAG = MainViewModel::class.java.simpleName

init {
Log.d(TAG, "MainViewModel init")
}
}

MainActivity에서 사용할 MainViewModel 클래스를 만들었다.

1) 이 MutableLiveData를 CustomEditText와 양방향 데이터 바인딩을 할 때 사용할 것이다.


5. activity_main.xml

<?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"
xmlns:bjs="http://schemas.android.com/apk/res-auto"
>

<data>
<variable
name="mainViewModel"
type="com.antwhale.sample.customdatabinding.MainViewModel"/>
</data>

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

<com.antwhale.sample.customdatabinding.CustomEditText
android:layout_width="0dp"
android:layout_height="wrap_content"
app:content="@={mainViewModel.content}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>


</androidx.constraintlayout.widget.ConstraintLayout>

</layout>

app:content="@{mainViewModel.content}" 가 빨간줄일 것이다.

아직 데이터바인딩 속성을 정의하지 않아서 그렇다.

좀만 기다리자.


6. MainActivity.class

class MainActivity : AppCompatActivity() {
val TAG = MainActivity::class.java.simpleName

private lateinit var binding: ActivityMainBinding    //1
val mainViewModel : MainViewModel by viewModels()    //2

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

binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.mainViewModel = mainViewModel    //3

mainViewModel.content.observe(this) {    //4
Log.d(TAG, "content: $it")
}

}
}

1) MainActivity도 데이터바인딩 사용

2) MainViewModel 선언 및 초기화

3) xml에 MainViewModel 전달

4) MainViewModel의 MutableLiveData 관찰 (CustomEditText의 값을 관찰하게된다.)


7. BindingAdapter 정의

BindingAdapter.class 파일을 만들어서 BindingAdapter의 속성들을 정의해주었다.

이때, 모든 fun은 전역으로 정의되어야한다.

@BindingAdapter("content")    //1
fun setContentOnEditText(view: CustomEditText, newValue: String) {
val oldValue = view.editText.text.toString()    

if(oldValue != newValue) {    //2
view.editText.setText(newValue)
}
}

@BindingAdapter("contentAttrChanged")    //3
fun setInverseBindingListenerOnEditText(view: CustomEditText, listener: InverseBindingListener?) {
val watcher = object : TextWatcher {    //4
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}

override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}

override fun afterTextChanged(p0: Editable?) {
listener?.onChange()    //5
}
}
view.editText.addTextChangedListener(watcher)    //6
}

@InverseBindingAdapter(attribute = "content", event = "contentAttrChanged")    //7
fun getContentOnEditText(view: CustomEditText) = view.editText.text.toString()


가장 중요한 CustomEditText의 바인딩 어댑터를 정의하는 부분이다.

1) 단방향 데이터바인딩에 사용하는 바인딩 어댑터이다.

- 속성의 이름은 "content"라고 했고 MainViewModel의 MutableLiveData가 변경되면 CustomEditText의 값도 변경되게하는 바인딩 어댑터이다.

2) 이전 값과 비교하여 값이 달라졌을 때만 바인딩 어댑터가 작동하게한다.

- 이렇게 하지않으면 무한루프가 돈다;;

3) CustomEditText에 양방향 데이터 바인딩을 위한 리스너를 등록하는 바인딩 어댑터이다.

4) 이번 예제에서 양방향 데이터 바인딩에 가장 관련있는 뷰가 EditText이기 때문에 TextWatcher를 정의해준다.

5) afterTextChanged()에 InverseBindingListener.onChange()를 호출한다.

- 이렇게해야 언제 양방향 데이터바인딩일 발생할지 알 수가 있다.

6) 정의한 TextWatcher를 CustomEditText에 있는 EditText에 등록해준다.

7) 양방향 데이터바인딩에 사용되는 바인딩 어댑터이다.

- View에 변경된 값이 MainViewModel의 MutableLiveData로 전달이 되기 때문에 InverseBindingAdapter라고 한다.

- event 매개변수를 입력하여 언제 InverBindingAdapter가 작동하는지 알려준다.


처음보면 좀 어려울 수 있는데 데이터바인딩의 개념에 대해 잘 이해하고있으면 부드럽게 넘어가는 개념이다.

나도 여러 블로그를 참고하면서 이 예제를 작성만해봤지 실무에서 사용해본 적은 없다.

다음에 어쩔 수 없이 커스텀뷰를 만들어야할 때는 꼭 사용해봐야겠다. 





Share:
Read More