【问题标题】:Progress in SeekBar and EditText with LiveData使用 LiveData 在 SeekBar 和 EditText 中取得进展
【发布时间】:2020-03-11 15:30:27
【问题描述】:

我有 TextInput 和 SeekBar。

我需要通过文本输入值(数字)更改搜索器的值,并通过搜索器更改值更改文本值。使用 View-Model 和 Live 数据绑定

所以,我在 viewmodel 类中有两个中介实时数据

视图模型:

val progress: MediatorLiveData<Int> by lazy { MediatorLiveData<Int>() }
val progressText: MediatorLiveData<String> by lazy { MediatorLiveData<String>() }

在init范围内相互观察。

init {
  progress.apply { addSource(progressText) { postValue(it.toInt()) }}
  progressText.apply { addSource(progress) { postValue(it.toString()) }}
}

xml和数据绑定是这样的:

<AppCompatSeekBar android:id="@+id/seekbar"
            style="@style/SeekerItemStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:progress="@={viewModel.progress}" />

 <EditText android:id="@+id/input_progress"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={viewModel.progressText}" />

(考虑输入文本)

但是,这里我遇到了无限循环,因为一个中介的变化会影响另一个中介的变化,而另一个中介的变化又会回到一个。

这里有解决这个逻辑的最佳实践和更聪明的解决方案吗?谢谢。

【问题讨论】:

    标签: android kotlin mvvm viewmodel android-livedata


    【解决方案1】:
    1. 这里不需要MediatorLiveData - MutableLiveData 就足够了,因为您只需要能够更改值。
    2. 对于progress,只有一个LiveData 就足够了,而且它应该在里面保存一个Int 值。

    现在是代码:

    import androidx.lifecycle.MutableLiveData
    import androidx.lifecycle.ViewModel
    
    open class BasicViewModel: ViewModel() {
        val progress: MutableLiveData<Int> by lazy { MutableLiveData<Int>() }
    }
    

    您的布局可能如下所示(请注意如何将viewModel.progress 转换为String for EditText

    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        <data>
            <variable
                name="viewModel"
                type="BasicViewModel"/>
        </data>
    
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <EditText android:id="@+id/input_progress"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="numberDecimal"
                android:text="@={`` + viewModel.progress}" />
    
            <SeekBar
                android:id="@+id/seekbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:progress="@={viewModel.progress}" />
    
        </LinearLayout>
    </layout>
    

    最后,您的Activity 可能如下:

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            val binding: ActivityMainBinding = DataBindingUtil.setContentView(
                    this, R.layout.activity_main)
            binding.setLifecycleOwner(this)
    
            val viewModel = ViewModelProviders.of(this).get(BasicViewModel::class.java)
            binding.viewModel = viewModel
        }
    }
    

    编辑:

    如果EditText 中的文本与SeekBar 中的值不同,则方法会有所不同。但是,拥有一个LiveData&lt;charSequence&gt; 对象来实现这样的场景仍然足够了。所以,让我们假设EditText 应该显示一个类似于progress/100 的浮点值。然后,它可能如下所示:

    BasicViewModel.kt

    package com.example.android.databinding.basicsample.data
    
    import androidx.lifecycle.LiveData
    import androidx.lifecycle.MutableLiveData
    import androidx.lifecycle.Transformations
    import androidx.lifecycle.ViewModel
    
    open class BasicViewModel: ViewModel() {
        val progressLiveData = MutableLiveData("0.0")
    
        fun updateEditText(percentage: Int) {
            progressLiveData.value = percentage.toFloat().div(100).toString()
        }
    
        fun onEditTextTyped(): LiveData<Int> {
            return Transformations.switchMap(progressLiveData, {
                val liveData = MutableLiveData<Int>()
                try {
                    liveData.value = it.toString().toFloat().times(100f).toInt()
                } catch (e: NumberFormatException) {
                    // reset the progress bar if the progress text is invalid
                    liveData.value = 0
                }
                liveData
            })
        }
    }
    

    activity_main.xml

    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        <data>
            <variable
                name="viewModel"
                type="com.example.android.databinding.basicsample.data.BasicViewModel"/>
        </data>
    
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <androidx.appcompat.widget.AppCompatEditText
                android:id="@+id/input_progress"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@={viewModel.progressLiveData}" />
    
            <SeekBar
                android:id="@+id/seekbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:progress="@{viewModel.onEditTextTyped}"
                android:onProgressChanged="@{(seekBar, progress, fromUser) -> viewModel.updateEditText(progress)}" />
    
        </LinearLayout>
    </layout>
    

    【讨论】:

      【解决方案2】:

      您可以使用Transformations.distinctUntilChanged(liveData)

      创建一个新的 LiveData 对象,该对象在源 LiveData 值更改之前不会发出值。如果 equals() 产生 false,则认为该值已更改。

      因此,通过使用distinctUntilChanged,我们可以通过检查源实时数据的值是否已更改来停止您的无限循环。


      如果您使用的是 livedata-ktx 库,您可以使用以下代码

      init {
         progress.apply { addSource(progressText.distinctUntilChanged()) { postValue(it.toInt()) }}
         progressText.apply { addSource(progress.distinctUntilChanged()) { postValue(it.toString()) }}
      }
      

      如果你没有使用 livedata-ktx 库,你可以使用下面的代码

      init {
         progress.apply { addSource(Transformations.distinctUntilChanged(progressText)) { postValue(it.toInt()) }}
         progressText.apply { addSource(Transformations.distinctUntilChanged(progress)) { postValue(it.toString()) }}
      }
      

      【讨论】:

        【解决方案3】:

        您可以分别为搜索者和文本制作 2 对 MediatorLiveData-LiveData。例如,第一个 MediatorLiveData 观察搜索栏的变化,当应用更改时,它会更改 EditText 的 LiveData,反之亦然:您有 MediatorLiveData,它会在每次 EditText 中发生更改时更改 SeekBar 的 LiveData。

        【讨论】:

          猜你喜欢
          • 2021-08-07
          • 1970-01-01
          • 1970-01-01
          • 2014-01-15
          • 1970-01-01
          • 1970-01-01
          • 2023-04-04
          • 1970-01-01
          • 2014-07-31
          相关资源
          最近更新 更多