안녕하세요.
이번 포스팅에서는 xml에서 데이터 바인딩이 가능한 다양한 뷰들을 알아보겠습니다.
언어: 코틀린
sdk vsersion
- compile: 33
- min: 21
- target: 33
우선 프래그먼트를 하나 생성해 줍니다.
DataBindingPracticeFragment.kt
@AndroidEntryPoint
class DataBindingPracticeFragment : Fragment() {
private var _binding: FragmentDataBindingPracticeBinding? = null
private val binding get() = _binding!!
private lateinit var viewModel: DataBindingPracticeViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
_binding = FragmentDataBindingPracticeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this).get(DataBindingPracticeViewModel::class.java)
binding.viewModel = viewModel
binding.lifecycleOwner = viewLifecycleOwner
}
}
xml을 data binding layout으로 작성해 줍니다.
fragment_data_binding_practice.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.example.laboratory.DataBindingPracticeFragment" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".DataBindingPracticeFragment">
...
</LinearLayout>
</layout>
가장 기본적인 text입니다.
DataBindingPracticeViewModel.kt
class DataBindingPracticeViewModel : ViewModel() {
...
private val _testText = MutableLiveData("test")
val testText: LiveData<String> get() = _testText
...
}
fragment_data_binding_practice.xml
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:text="@{viewModel.testText}" />
onClick입니다.
DataBindingPracticeViewModel.kt
class DataBindingPracticeViewModel : ViewModel() {
...
fun onButtonAddClicked() {
}
fun onButtonRemoveClicked() {
}
...
}
fragment_data_binding_practice.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".DataBindingPracticeFragment">
...
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:text="추가"
android:onClick="@{() -> viewModel.onButtonAddClicked()}"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:text="삭제"
android:onClick="@{() -> viewModel.onButtonRemoveClicked()}"/>
...
</LinearLayout>
EditText - onTextChanged입니다.
DataBindingPracticeViewModel.kt
class DataBindingPracticeViewModel : ViewModel() {
...
private val _testText = MutableLiveData("")
val testText: LiveData<String> get() = _testText
fun onTextInput(input: CharSequence) {
_testText.value = input.toString()
}
...
}
fragment_data_binding_practice.xml
<EditText
android:layout_width="match_parent"
android:layout_height="40dp"
android:onTextChanged="@{(s, start, before, count) -> viewModel.onTextInput(s)}" />
Progress Bar입니다.
DataBindingPracticeViewModel.kt
class DataBindingPracticeViewModel : ViewModel() {
...
private val _progress = MutableLiveData(0)
val progress: LiveData<Int> get() = _progress
private val _progressMax = MutableLiveData(0)
val progressMax: LiveData<Int> get() = _progressMax
...
}
fragment_data_binding_practice.xml
<ProgressBar
android:id="@+id/loading_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="@{viewModel.progressMax}"
android:progress="@{viewModel.progress}" />
max와 progress값 모두 viewModel의 live data를 관찰합니다.
Spinner입니다.
data binding과 관련된 부분만 작성하고 구체적인 spinner 코드는 작성하지 않겠습니다.
spinner에 관한 내용은 아래 포스팅을 참고해 주세요.
https://it-of-fortune.tistory.com/33
DataBindingPracticeViewModel.kt
class DataBindingPracticeViewModel : ViewModel() {
...
private val _progressMax = MutableLiveData(0)
val progressMax: LiveData<Int> get() = _progressMax
...
fun onSpinnerItemSelected(parent: AdapterView<*>?, position: Int) {
if (parent != null) {
_progressMax.value = parent.getItemAtPosition(position).toString().toInt()
}
}
...
}
fragment_data_binding_practice.xml
<Spinner
android:id="@+id/max_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onItemSelected="@{(parent, view, position, id) -> viewModel.onSpinnerItemSelected(parent, position)}" />
visibility입니다.
DataBindingPracticeViewModel.kt
class DataBindingPracticeViewModel : ViewModel() {
...
private val _isListFull = MutableLiveData(false)
val isListFull: LiveData<Boolean> get() = _isListFull
private val _isListEmpty = MutableLiveData(true)
val isListEmpty: LiveData<Boolean> get() = _isListEmpty
...
}
fragment_data_binding_practice.xml
<data>
...
<import type="android.view.View" />
...
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".DataBindingPracticeFragment">
...
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:text="추가"
android:visibility="@{viewModel.isListFull() ? View.GONE : View.VISIBLE}"
android:onClick="@{() -> viewModel.onButtonAddClicked()}"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:text="삭제"
android:visibility="@{viewModel.isListEmpty() ? View.GONE : View.VISIBLE}"
android:onClick="@{() -> viewModel.onButtonRemoveClicked()}"/>
...
</LinearLayout>
android:visibility가 viewModel의 Boolean live data를 바라보며, 값에 따라 visibility가 재 적용됩니다.
이로써, 몇 가지 뷰에서 사용가능한 data binding을 알아보았습니다.
이를 이용해서 하나의 기능을 만들어 보겠습니다.
DataBindingPracticeViewModel.kt
class DataBindingPracticeViewModel : ViewModel() {
private val _progress = MutableLiveData(0)
val progress: LiveData<Int> get() = _progress
private val _progressMax = MutableLiveData(0)
val progressMax: LiveData<Int> get() = _progressMax
private val _testText = MutableLiveData("")
val testText: LiveData<String> get() = _testText
private val _textListLiveData = MutableLiveData<ArrayList<String>>()
val textListLiveData: LiveData<ArrayList<String>> get() = _textListLiveData
private val _isListFull = MutableLiveData(false)
val isListFull: LiveData<Boolean> get() = _isListFull
private val _isListEmpty = MutableLiveData(true)
val isListEmpty: LiveData<Boolean> get() = _isListEmpty
private val textList = ArrayList<String>()
fun onTextInput(input: CharSequence) {
if (input.isNotEmpty()) {
_testText.value = input.toString()
}
}
fun onButtonAddClicked() {
if (testText.value!!.isNotEmpty() && textList.size < progressMax.value!!) {
textList.add(testText.value.toString())
_progress.value = textList.size
_textListLiveData.postValue(textList)
}
setButtonVisibility()
}
fun onButtonRemoveClicked() {
if (textList.isNotEmpty()) {
textList.removeLast()
_progress.value = textList.size
_textListLiveData.postValue(textList)
}
setButtonVisibility()
}
private fun setButtonVisibility() {
_isListEmpty.postValue(textList.isEmpty())
_isListFull.postValue(textList.size >= progressMax.value!!)
}
fun onSpinnerItemSelected(parent: AdapterView<*>?, position: Int) {
if (parent != null) {
_progressMax.value = parent.getItemAtPosition(position).toString().toInt()
}
setButtonVisibility()
}
}
fragment_data_binding_practice.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="com.example.laboratory.DataBindingPracticeViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".DataBindingPracticeFragment">
<Spinner
android:id="@+id/max_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onItemSelected="@{(parent, view, position, id) -> viewModel.onSpinnerItemSelected(parent, position)}" />
<ProgressBar
android:id="@+id/loading_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="@{viewModel.progressMax}"
android:progress="@{viewModel.progress}" />
<EditText
android:layout_width="match_parent"
android:layout_height="40dp"
android:onTextChanged="@{(s, start, before, count) -> viewModel.onTextInput(s)}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:text="@{viewModel.testText}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:text="추가"
android:visibility="@{viewModel.isListFull() ? View.GONE : View.VISIBLE}"
android:onClick="@{() -> viewModel.onButtonAddClicked()}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:text="@{viewModel.textListLiveData.toString()}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:text="삭제"
android:visibility="@{viewModel.isListEmpty() ? View.GONE : View.VISIBLE}"
android:onClick="@{() -> viewModel.onButtonRemoveClicked()}"/>
</LinearLayout>
</layout>
의도한 동작은 아래와 같습니다.
1. 텍스트를 입력하면 그 아래에 입력한 텍스트가 출력된다.
2. 추가와 삭제 버튼을 통해 입력한 텍스트를 리스트에 추가 / 삭제한다.
3. 가장 위에 spinner를 통해 텍스트를 추가 가능한 최대 사이즈를 설정한다.
4. 현재 사이즈와 최대 사이즈를 확인해 추가와 삭제 버튼의 visibility를 통제한다.
5. 현재 사이즈와 최대 사이즈를 비교해 progress bar에 표시한다.
6. 리스트의 내용을 가운데에 출력한다.
테스트해 보겠습니다.
정상적으로 동작합니다.
이상 포스팅을 마치겠습니다.
감사합니다.
'Android Application > 응용 구현' 카테고리의 다른 글
안드로이드 리사이클러뷰 아이템 클릭(recyclerView item click)처리 (0) | 2023.04.04 |
---|---|
안드로이드 회차별 로또 정보 받아오기(retrofit2 + MVVM) (0) | 2023.04.02 |
Android Studio 연습용 어플 구현2 (사용자 입력2) (0) | 2020.05.26 |
Android Studio 연습용 어플 구현1 (사용자 입력) (0) | 2020.05.25 |
Android Studio 연습용 어플 구현 (Activity 전환) (0) | 2020.05.22 |
댓글