Development record of developer who study hard everyday.

레이블이 안드로이드데이터바인딩인 게시물을 표시합니다. 모든 게시물 표시
레이블이 안드로이드데이터바인딩인 게시물을 표시합니다. 모든 게시물 표시
, , , ,

안드로이드 커스텀뷰(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
, , ,

안드로이드 바인딩 어댑터로 Glide 사용하기 예제

 안드로이드 바인딩 어댑터로 Glide 사용하기 예제


요즘에 함수형프로그래밍이 대세이다.

그래서 회사에서 프로젝트를 할 때 최대한 리액티브하게 코드를 구현하도록 노력 중이다.

오늘은 제일 흔하게 사용되는 Glide로 이미지를 리액티브하게 보여주는 코드를 예제로 보여주려고 한다.

데이터바인 세팅하는 과정은 생략하겠다.

안드로이드 데이터바인딩 공식문서에 자세히 나와있으니 참고하길 바란다.

@BindingAdapter("imageUrl")
fun setImageUrl(imageView: ImageView, url:String?) {
url?.let {
Glide.with(imageView.context).load(url).into(imageView)
}
}

주의할 점은 setImageUrl 함수가 전역함수여야한다.

클래스 안에 들어가면 안된다는 뜻이다.

"imageUrl" 은 xml에서 사용할 속성이다.

setImageUrl 첫번째 매개변수는 내가 사용할 View의 타입을 변수로 넘겨준다.

두번째 매개변수로 url을 넘겨준다.

<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
imageUrl="@{selectedItem.heatImgPath}"
android:onClick="@{() -> fragment.showImageDialog(selectedItem.heatImgPath)}"
/>

내가 선언한 바인딩어댑터를 사용할 ImageView에 imageUrl 속성을 적어주면 된다.


Share:
Read More