英文:
Databinding, MaterialCardView should act like Radiogroup
问题
我试图实现两个 MaterialCardViews,它们应该像一个 Radiogroup 一样工作。因此,如果我点击其中一个,另一个应该取消选中。我正在使用 viewModel、liveData 和自定义的双向数据绑定来保存这些值以供以后使用(通过电子邮件发送)。
我已经成功编写了 .xml 文件并实现了选中逻辑,但在实现取消选中逻辑时遇到了困难。
## XML,缩短版以获得更好的可见性 ##
<layout
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="vm"
type="com.example.app.data.viewmodel.EmailViewModel" />
</data>
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardViewOne"
android:checkable="true"
android:clickable="true"
android:focusable="true"
<!-- 自定义双向数据绑定 -->
app:state_checked="@={vm.cardOptionOneChecked}">
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardViewTwo"
android:checkable="true"
android:clickable="true"
android:focusable="true"
<!-- 自定义双向数据绑定 -->
app:state_checked="@={vm.cardOptionTwoChecked}">
</com.google.android.material.card.MaterialCardView>
</layout>
## ViewModel ##
class EmailViewModel @ViewModelInject constructor(
@Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// Id = cardViewOne 的变量
val cardOptionOneChecked = MutableLiveData<Boolean>()
// Id = cardViewTwo 的变量
val cardOptionTwoChecked = MutableLiveData<Boolean>()
}
## CardViewAdapter.kt ##
@BindingAdapter("state_checked")
fun setStateChecked(view: MaterialCardView, liveData: MutableLiveData<Boolean>) {
if (view.isChecked != liveData.value) {
liveData.value = view.isChecked
}
}
@InverseBindingAdapter(attribute = "state_checked")
fun getStateChecked(view: MaterialCardView): Boolean {
return view.isChecked
}
// 我不知道这里应该有什么逻辑来使其工作!
// 当前的方法只是检查当前视图,没有做更多的事情。如何保存上次选中的值?
@BindingAdapter("state_checkedAttrChanged")
fun setCheckedAttrListener(
view: MaterialCardView,
attrChange: InverseBindingListener,
) {
view.apply {
setOnClickListener { view.isChecked = true }
setOnCheckedChangeListener { card, isChecked ->
if (card.isChecked && card != view) {
card.isChecked = false
}
}
attrChange.onChange()
}
}
我非常感谢任何帮助,非常感谢!
附注:如果有更好、更简单的方法来实现这一点,例如从视图告诉 viewModel 保存 isChecked,请告诉我。MaterialCardView 默认实现了 "isChecked",但没有逻辑。
英文:
I am trying to implement two MaterialCardViews that should act like a Radiogroup. So if I click one, the other should be unchecked. I am using viewModel, liveData and custom two-way data binding to save these values for later purpose (sending per email).
I had success writing the .xml and implementing the check logic, but I struggle implementing uncheck logic.
XML, short version for better visibility
<layout
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="vm"
type="com.example.app.data.viewmodel.EmailViewModel" />
</data>
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardViewOne"
android:checkable="true"
android:clickable="true"
android:focusable="true"
<!-- Custom Two way databinding -->
app:state_checked="@={vm.cardOptionOneChecked}"
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardViewTwo"
android:checkable="true"
android:clickable="true"
android:focusable="true"
<!-- Custom Two way databinding -->
app:state_checked="@={vm.cardOptionTwoChecked}">
</com.google.android.material.card.MaterialCardView>
</layout>
ViewModel
class EmailViewModel @ViewModelInject constructor(
@Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// Variable for Id = cardViewOne
val cardOptionOneChecked = MutableLiveData<Boolean>()
// Variable for Id = cardViewTwo
val cardOptionTwoChecked = MutableLiveData<Boolean>()
}
CardViewAdapter.kt
@BindingAdapter("state_checked")
fun setStateChecked(view: MaterialCardView, liveData: MutableLiveData<Boolean>) {
if (view.isChecked != liveData.value) {
liveData.value = view.isChecked
}
}
@InverseBindingAdapter(attribute = "state_checked")
fun getStateChecked(view: MaterialCardView,): Boolean {
return view.isChecked
}
// I don't know what logic belongs here to make it work!
// Current approach just checks the current view and does nothing more. How can I save the last
// checked value?
@BindingAdapter("state_checkedAttrChanged")
fun setCheckedAttrListener(
view: MaterialCardView,
attrChange: InverseBindingListener,
) {
view.apply {
setOnClickListener { view.isChecked = true }
setOnCheckedChangeListener { card, isChecked ->
if (card.isChecked && card != view) {
card.isChecked = false
}
}
attrChange.onChange()
}
}
I appreciate every help, thank you very much!
P.S: If there is a better and easier way to achieve this e.g. telling the viewModel from the view to save isChecked, please inform me. MaterialCardView has implemented "isChecked" by default but no logic.
答案1
得分: 0
好的,我已经解决了这个问题:
首先,修改绑定适配器
实际上,我没有看到使用双向数据绑定来实现上述所写情况的任何方法。以下是新的绑定适配器代码:
// View = 被点击的 MaterialCard,liveData = ViewModel 中的值
@BindingAdapter("state_checked")
fun setStateChecked(view: MaterialCardView, liveData: MutableLiveData<Boolean>) {
if (view.isChecked != liveData.value) {
if (liveData.value != null) {
view.isChecked = liveData.value!!
}
}
}
其次,修改 XML 布局,因为我们不再使用双向数据绑定
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="vm"
type="com.example.app.data.viewmodel.EmailViewModel" />
</data>
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardViewOne"
android:checkable="true"
android:clickable="true"
android:focusable="true"
<!-- 删除了 "=" -->
app:state_checked="@{vm.cardOptionOneChecked}">
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardViewTwo"
android:checkable="true"
android:clickable="true"
android:focusable="true"
<!-- 删除了 "=" -->
app:state_checked="@{vm.cardOptionTwoChecked}">
</com.google.android.material.card.MaterialCardView>
</layout>
第三,修改 ViewModel
class EmailViewModel @ViewModelInject constructor(
@ApplicationContext context: Context,
@Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val cardOptionOneChecked = MutableLiveData<Boolean>()
val cardOptionTwoChecked = MutableLiveData<Boolean>()
// 添加了
fun firstCardClicked() {
cardOptionOneChecked.value = true
cardOptionTwoChecked.value = false
}
fun secondCardClicked() {
cardOptionOneChecked.value = false
cardOptionTwoChecked.value = true
}
}
第四,为 XML 或 Fragment(这里是 Fragment)添加点击监听器
cardViewOne.setOnClickListener {
viewModel.firstCardClicked()
}
cardViewTwo.setOnClickListener {
viewModel.secondCardClicked()
}
如果有人有任何问题,请在评论中写下,我会提供帮助。
英文:
Okay, I've solved the Problem:
First, Change Binding Adapter
I actually don't saw any way to use two-way data binding to achieve the above written case. Here is the new Binding Adapter
// View = Clicked MaterialCard, liveData = value in viewModel
@BindingAdapter("state_checked")
fun setStateChecked(view: MaterialCardView, liveData: MutableLiveData<Boolean>) {
if (view.isChecked != liveData.value) {
if (liveData.value != null) {
view.isChecked = liveData.value!!
}
}
}
Second, Change XML Layout, because we don't use two-way data binding anymore
<layout
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="vm"
type="com.example.app.data.viewmodel.EmailViewModel" />
</data>
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardViewOne"
android:checkable="true"
android:clickable="true"
android:focusable="true"
<!-- Deleted "=" -->
app:state_checked="@{vm.cardOptionOneChecked}"
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardViewTwo"
android:checkable="true"
android:clickable="true"
android:focusable="true"
<!-- Deleted "=" -->
app:state_checked="@{vm.cardOptionTwoChecked}">
</com.google.android.material.card.MaterialCardView>
</layout>
Third, Change viewmodel
class EmailViewModel @ViewModelInject constructor(
@ApplicationContext context: Context,
@Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val cardOptionOneChecked = MutableLiveData<Boolean>()
val cardOptionTwoChecked = MutableLiveData<Boolean>()
// Added
fun firstCardClicked() {
cardOneChecked.value = true
cardTwoChecked.value = false
}
fun secondCardClicked() {
cardOneChecked.value = false
cardTwoChecked.value = true
}
}
Fourth, add clickListener to XML or Fragment (here Fragment)
cardViewOne.setOnClickListener {
viewModel.firstCardClicked()
}
cardViewTwo.setOnClickListener {
viewModel.secondCardClicked()
}
If someone has any questions, just write it in the comments, I will help.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论