티스토리 뷰
이번에는 데이터 바인딩에 대하여 포스팅을 해보려고 합니다.
데이터 바인딩 (DataBinding)은 Android Jetpack 라이브러리 중 하나로,
UI를 바인딩할 수 있는 지원 라이브러리입니다.
선언문을 사용하여 레이아웃의 구성 요소를 앱의 데이터 소스로 이동 포맷할 수 있습니다.
이렇게 정의되어 있습니다.
Activity, Fragment 등에서 직접 UI 작업을 진행했던 것을 Data Binding을 활용하여 데이터에 따라 UI를 변경하는 방식입니다.
1. 사용 준비
android {
compileSdk 32
defaultConfig {
applicationId "com.example.standardstudy"
minSdk 26
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
dataBinding true
}
...
}
...
Data Binding을 사용하기 위해서는 app 수준의 build.gradle에 buildFeatures를 해당 위치에 넣어 주시면 끝입니다.
그다음은 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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".databinding.BindingTestActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
원래 사용하던 최상단의 뷰를 <layout>으로 감싸줍니다.
지금은 뷰 밖에 없지만 아래에서 <layout> 태그 안에 <data>도 추가될 예정입니다
다음은 Activity 입니다.
Activity에서 데이터 바인딩을 세팅해주어야 하는데 2가지 방식이 존재합니다.
class BindingTestActivity : AppCompatActivity() {
private lateinit var binding : ActivityBindingTestBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityBindingTestBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
위의 두 단계를 진행하면 다음과 같은 형식으로 된 클래스 파일이 존재할 겁니다.
저의 경우 BindingTestActivity이므로 ActivityBindingTestBinding로 나옵니다.
그다음 inflate의 매개변수로 LayoutInflate를 넣어주면 됩니다.
주의할 점은 기존에 있던 setContentView안에 R.layout.ooo으로 된 부분 대신 binding.root로 넣어주셔야 데이터 바인딩을 이용할 수 있습니다.
private val binding : ActivityBindingTestBinding by lazy { ActivityBindingTestBinding.inflate(layoutInflater) }
저는 이 방식을 이용할 땐 전역 변수로 위와 같이 선언해 둡니다.
class BindingTestActivity : AppCompatActivity() {
private lateinit var binding : ActivityBindingTestBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_binding_test)
}
}
두 번째 방식은 DatabindingUtil을 이용하는 방식입니다.
기존에 있던 setContentView를 조금만 수정하면 사용 가능해서 이 부분이 편할 수도 있습니다
어떤 방식을 사용할지는 개인적인 취향에 맞게 정하시면 됩니다.
Fragment나 Adapter등에서도 크게 사용법은 다르지 않아서 따로 다루지는 않겠습니다.
2. Activity에서 Binding 사용하기
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
app:layout_constraintTop_toBottomOf="@id/textView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="click"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
위와 같이 세 개의 뷰를 만들었습니다.
val textView = findViewById<TextView>(R.id.textView)
textView.text = "txt"
binding.textView.text = "text"
위의 1~2 라인은 기본적으로 뷰를 연결해서 사용하는 방식입니다.
마지막 라인은 데이터 바인딩을 이용하여 사용하는 방법입니다.
binding 변수를 통해서 xml에 정의된 모든 뷰를 접근할 수 있습니다.
여기서 주의할 점은 id가 정의되어 있지 않으면 바로 접근은 불가능하고,
만약에 xml에 view의 id를 text_view로 지었다고 해도 binding에서는 카멜 표기법으로 변환되어 textView로 변경됩니다.
저는 검색할 때 그런 점이 불편해서 카멜 표기법으로 xml에서도 동일하게 id를 만드는 편입니다.
3. xml에서 Data 정의 추가
<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="number"
type="androidx.databinding.ObservableInt" />
<variable
name="activity"
type="com.example.standardstudy.databinding.BindingTestActivity" />
<import type="android.view.View"/>
</data>
...
data를 정의하는 방법은 3가지 정도로 구분할 수 있습니다.
첫 번째는 <variable>에서 type을 기본 제공하는 클래스로 정의해서 사용하는 방법입니다.
두 번째는 <variable>에서 사용자가 정의한 클래스의 위치를 지정하여 사용하는 방법입니다.
세 번째는 <import>에서 타입을 제공받아서 사용하는 방법입니다.
<variable>과 <import> 방식의 차이는
<variable>의 경우 activity에서 해당 클래스의 맞는 값을 넣어주어야 하고,
<import>는 activity와 별도 동작 없이 사용 가능합니다.
이제 데이터를 사용해봅시다.
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".databinding.BindingTestActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`` + number}"
android:textSize="30sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
android:text="@{number%2==0 ? `짝수입니다.` : `홀수입니다.`}"
android:visibility="@{number > 10 ? View.VISIBLE : View.GONE, default=visible}"
app:layout_constraintTop_toBottomOf="@id/textView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="click"
android:onClick="@{()->activity.setIncrease()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
DataBinding의 값을 사용할 땐 기본적으로 @{}를 이용해서 값을 넣어줍니다.
하나씩 봅시다
첫 번째 TextView에서는 "@{`` + number}"를 사용하였습니다.
우선 ``을 사용한 이유는 number가 ObserverableInt 타입이어서 String으로 만들기 위해서입니다.
String.valueOf(numger)를 넣으셔도 무관합니다.
그냥 Integer를 사용하지 않고 ObserverableInt를 사용한 이유는
Integer타입을 이용하게 되면 한 번만 적용되고 변경되었을 때 코드에서 바인딩 값을 수정해주어야 합니다.
그렇게 하지 않고 Observable 한 타입을 이용하면 한 번만 바인딩에 값을 할당해 주면
데이터가 변경될 때마다 UI가 자동으로 수정됩니다.
이점을 잘 고려해서 필요에 따라서 Observable타입을 이용할지 말지 정하시면 됩니다.
무조건 Observable을 사용할 필요는 없습니다
android:text="@{number%2==0 ? `짝수입니다.` : `홀수입니다.`}"
android:visibility="@{number > 10 ? View.VISIBLE : View.GONE, default=visible}"
두 번째에서는 비교 연산자와 삼항 연산자를 이용해봤습니다.
사용 가능한 연산자들은 아래와 같습니다.
- Mathematical + - / * %
- String concatenation +
- Logical && ||
- Binary & | ^
- Unary + - ! ~
- Shift >> >>> <<
- Comparison == > < >= <= (Note that < needs to be escaped as <)
- instanceof
- Grouping ()
- Literals - character, String, numeric, null
- Cast
- Method calls
- Field access
- Array access []
- Ternary operator ?:
DataBinding을 이용하는 건 삼항 연산자 정도까지만 이용하고 복잡한 로직은 함수로 만들어서 사용해야 합니다.
만약 strings나 color, drawable을 이용하고 싶을 경우에는
<string name="increase">누적 : %d</string>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{@string/increase(number)}"
android:textSize="30sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
이런 식으로 이용이 가능합니다.
마지막은 함수 호출 관련입니다.
fun setIncrease() {
number.set(number.get() + 1)
}
함수는 위와 같이 구현되어있습니다.
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="click"
android:onClick="@{()->activity.setIncrease()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
() -> activity.setIncrease()를 통해 함수 호출이 가능합니다.
fun setIncrease(view: View) {
number.set(number.get() + 1)
}
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="click"
android:onClick="@{activity::setIncrease}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
다른 방식은 activity::setIncrease를 사용하는 것인데요
차이점은 함수를 보면 view: View가 있는 것을 알 수 있습니다.
둘의 차이는 이렇게 이해하시면 편할 것 같습니다.
val listener : () -> Unit = {
// 생략
}
binding.button.setOnClickListener {
listener.invoke()
}
val onClickListener = object : View.OnClickListener {
override fun onClick(view: View) {
// 생략
}
}
binding.button.setOnClickListener(onClickListener)
필요에 따라 맞게 사용하면 될 것 같습니다.
이제 xml에서의 사용법은 알아봤으니 activity에서 작업을 진행해 줍니다.
private var number : ObservableInt = ObservableInt(1)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.number = number
binding.activity = this
}
<variable>로 정의한 number와 activity는 위와 같이 초기 값을 넣어주어야 정상적으로 작동을 하게 됩니다.
4. BindingAdapter
지금까지 알아본 방식으로 UI를 위해 작성했던 코드를 많이 줄일 수 있었을 겁니다.
그런데 onClick처럼 제공하지는 않지만 자주 사용하는 로직이 있다고 하면
매번 다른 Activity에 정의하는 건 번거로운 일입니다.
그래서 있는 게 BindingAdapter입니다
@BindingAdapter("increaseText")
fun increaseText(view: TextView, number: Int) {
view.text = view.context.getString(R.string.increase, number)
}
@BindingAdapter("increaseButtonClick")
fun increaseButtonClickListener(view: View, activity: Activity) {
view.setOnClickListener {
activity.toast("increaseButton을 클릭하였습니다.")
}
}
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:increaseText="@{number}"/>
...
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="click"
android:onClick="@{()->activity.setIncrease(2)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:increaseButtonClick="@{activity}"/>
단순 예제로 작성했기 때문에 의미가 있을까 싶겠지만..
@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, error: Drawable) {
Picasso.get().load(url).error(error).into(view)
}
위와 같이 이미지를 로딩하는 로직을 사용한다고 하면 활용도가 좋아지겠죠?ㅎㅎ
데이터 바인딩 사용하면 편리하고 좋은데 처음부터 모든 UI 코드를 xml에 데이터 바인딩 적용하는 것은 추천하지 않습니다.
잘 못 사용하게 되면 의도치 않게 UI가 변경되고 특히 Observable을 이용할 때 예상치 못 한 오류가 생길 수 있습니다.
이 부분은 코드로 작성한 게 아니어서 오류 찾기가 쉽지 않을 수 있으므로 많이 사용해보고
자신의 맞는 사용법을 익혀서 하나씩 적용해보는 것을 추천드립니다.
참고 자료
공식 문서 : https://developer.android.com/topic/libraries/data-binding?hl=ko
데이터 결합 라이브러리 | Android 개발자 | Android Developers
데이터 결합 라이브러리 Android Jetpack의 구성요소. 데이터 결합 라이브러리는 프로그래매틱 방식이 아니라 선언적 형식으로 레이아웃의 UI 구성요소를 앱의 데이터 소스와 결합할 수 있는 지원
developer.android.com
'안드로이드 > 코드' 카테고리의 다른 글
Compose 기초 2 : Text, Image, LazyColumn + Card (0) | 2022.06.30 |
---|---|
네이버 검색 API 사용 (0) | 2022.06.09 |
도로명 주소 개발센터 API 사용해 보기 4: AAC 사용해 보기 (0) | 2022.05.17 |
도로명 주소 개발센터 API 사용해 보기 3: Coroutine 사용해 보기 (0) | 2022.05.11 |
도로명 주소 개발센터 API 사용해 보기 2:DI + Hilt 사용해 보기 (0) | 2022.05.07 |
- Total
- Today
- Yesterday
- Compose ModalBottomSheetLayout
- 안드로이드
- LazyColumn
- Duplicate class found error
- Compose BottomSheet
- WorkManager
- Duplicate class fond 에러
- compose
- 웹뷰
- Fast api
- Compose Naver Map
- Kotlin
- Android Compose
- WebView
- Compose BottomSheetScaffold
- Compose QRCode Scanner
- 안드로이드 구글 지도
- Retrofit
- Compose MotionLayout
- Compose BottomSheetDialog
- Pokedex
- Android
- Compose ConstraintLayout
- Compose 네이버 지도
- Row
- column
- Worker
- Gradient
- 포켓몬 도감
- Compose 네이버 지도 api
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |