【问题标题】:Why does LiveData only effect main layout and doesn't LiveData effect child layout UI in Android Studio?为什么 LiveData 只影响 Android Studio 中的主布局而不影响子布局 UI?
【发布时间】:2020-09-10 08:09:10
【问题描述】:

FragmentHome加载layout_home.xml,layout_home.xml显示一个recyclerview和一个名为btnMain的按钮

recyclerview包含项目布局layout_voice_item.xml,它显示一个名为btnChild的按钮。

我使用displayCheckBox : LiveData<Boolean> 来控制是否同时显示btnMain 和btnChild,代码android:visibility="@{!aHomeViewModel.displayCheckBox? View.VISIBLE: View.GONE}"

我发现btnMain改变displayCheckBox的值可以显示或者不显示,但是btnChild一直显示,为什么?

FragmentHome.kt

class FragmentHome : Fragment() {

    private lateinit var binding: LayoutHomeBinding

    private val mHomeViewModel by lazy {
        getViewModel {
            HomeViewModel(provideRepository(mContext))
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate(
            inflater, R.layout.layout_home, container, false
        )
        binding.lifecycleOwner = this.viewLifecycleOwner
        binding.aHomeViewModel=mHomeViewModel

        val adapter = VoiceAdapters(mHomeViewModel)
        binding.mvoiceList.adapter=adapter
        mHomeViewModel.listVoiceBySort.observe(viewLifecycleOwner){
          adapter.submitList(it)
        }
        ...
        return binding.root
    }
}

HomeViewModel.kt

class HomeViewModel(private val mDBVoiceRepository: DBVoiceRepository) : ViewModel() {

    private val _displayCheckBox = MutableLiveData<Boolean>(true)
    val displayCheckBox : LiveData<Boolean> = _displayCheckBox

    fun setCheckBox(isDisplay:Boolean){
        _displayCheckBox.value = isDisplay
    }

    ...
}

VoiceAdapters.kt

class VoiceAdapters (private val aHomeViewModel: HomeViewModel):
        ListAdapter<MVoice, VoiceAdapters.VoiceViewHolder>(MVoiceDiffCallback()) {
    ...
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VoiceViewHolder {
        return VoiceViewHolder(
            LayoutVoiceItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        )
    }

    override fun onBindViewHolder(holder: VoiceViewHolder, position: Int) {
        val aMVoice = getItem(position)
        holder.bind(aHomeViewModel, aMVoice)
    }

    inner class VoiceViewHolder (private val binding: LayoutVoiceItemBinding):
          RecyclerView.ViewHolder(binding.root) {

        fun bind(mHomeViewModel: HomeViewModel, aMVoice: MVoice) {
            binding.aHomeViewModel = mHomeViewModel
            binding.executePendingBindings()
        }

    }

    ...
}
class MVoiceDiffCallback : DiffUtil.ItemCallback<MVoice>() {
    ...
}

layout_home.xml

<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>
        <import type="android.view.View" />

        <variable name="aHomeViewModel"
            type="info.dodata.voicerecorder.viewcontrol.HomeViewModel" />
    </data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/mvoice_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            tools:listitem="@layout/layout_voice_item"            
        />

      <Button
            android:id="@+id/btnMain"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"         
            android:visibility="@{!aHomeViewModel.displayCheckBox? View.VISIBLE: View.GONE}"            
        />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

layout_voice_item.xml

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>
    <import type="android.view.View" />
    <variable name="aHomeViewModel"
        type="info.dodata.voicerecorder.viewcontrol.HomeViewModel" />
</data>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <Button
        android:id="@+id/btnChild"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="@{!aHomeViewModel.displayCheckBox? View.VISIBLE: View.GONE}"        
    />

</LinearLayout>
</layout>

【问题讨论】:

    标签: android kotlin android-jetpack


    【解决方案1】:

    LifecycleOwner 发送给LayoutVoiceItemBinding 可能会有所帮助。
    voiceViewHolder.binding.lifecycleOwner = yourLifecycleOwner

    【讨论】:

      【解决方案2】:

      视图持有者模式并不总是 android 的一部分,在它被广泛采用之前,简单的列表实现会导致为提交列表中的每个值渲染一个膨胀的视图。 这是非常低效的内存。

      通过采用视图持有者模式,视图(视觉项目)仅膨胀到屏幕上可见的最大值(以及一两个额外的平滑滚动)

      但是,适配器需要知道列表数据的变化。

      https://developer.android.com/guide/topics/ui/layout/recyclerview#Adapter

      如果列表需要更新,请在列表中调用通知方法 RecyclerView.Adapter 对象,例如 notifyItemChanged()。布局 manager 然后重新绑定任何受影响的视图持有者,允许他们的数据 更新。

      观察displayCheckBox : LiveData&lt;Boolean&gt; 并调用adapter.notifyItemChanged 可能是当前的问题。

      我留下另一个参考,重申视图绑定不会观察实时数据,但会减少样板代码。

      https://medium.com/androiddevelopers/android-data-binding-recyclerview-db7c40d9f0e4

      还剩下什么? 现在处理来自 RecyclerView 的所有样板 你剩下要做的就是最困难的部分:从 UI 加载数据 线程,当有数据变化时通知适配器等。 Android 数据绑定只是减少了无聊的部分。

      【讨论】:

        猜你喜欢
        • 2014-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多