본문 바로가기
Android Application/응용 구현

안드로이드의 다양한 데이터 바인딩(editText, Progress bar...)

by sdchjjj 2023. 4. 2.
728x90

안녕하세요.

이번 포스팅에서는 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

 

안드로이드 스피너(spinner) 사용

안녕하세요. 이번 포스팅에서는 spinner를 사용해 보겠습니다. 언어: 코틀린 sdk vsersion - compile: 33 - min: 21 - target: 33 프래그먼트를 하나 생성하여 아래와 같이 작성합니다. fragment_spinner.xml SpinnerFragme

it-of-fortune.tistory.com

 

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. 리스트의 내용을 가운데에 출력한다.

 

테스트해 보겠습니다.

figure1. run_1

 

figure2. run_2

 

figure3. run_3

 

figure4. run_4

 

정상적으로 동작합니다.

 

이상 포스팅을 마치겠습니다.

 

감사합니다.

728x90

댓글