数据绑定,MaterialCardView 应该像 Radiogroup 一样起作用

huangapple go评论71阅读模式
英文:

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

&lt;layout
xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;&gt;
&lt;data&gt;
&lt;variable
name=&quot;vm&quot;
type=&quot;com.example.app.data.viewmodel.EmailViewModel&quot; /&gt;
&lt;/data&gt;
&lt;com.google.android.material.card.MaterialCardView
android:id=&quot;@+id/cardViewOne&quot;
android:checkable=&quot;true&quot;
android:clickable=&quot;true&quot;
android:focusable=&quot;true&quot;
&lt;!-- Custom Two way databinding --&gt;
app:state_checked=&quot;@={vm.cardOptionOneChecked}&quot;
&lt;/com.google.android.material.card.MaterialCardView&gt;
&lt;com.google.android.material.card.MaterialCardView
android:id=&quot;@+id/cardViewTwo&quot;
android:checkable=&quot;true&quot;
android:clickable=&quot;true&quot;
android:focusable=&quot;true&quot;
&lt;!-- Custom Two way databinding --&gt;
app:state_checked=&quot;@={vm.cardOptionTwoChecked}&quot;&gt;
&lt;/com.google.android.material.card.MaterialCardView&gt;
&lt;/layout&gt;

ViewModel

class EmailViewModel @ViewModelInject constructor(
@Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// Variable for Id = cardViewOne
val cardOptionOneChecked = MutableLiveData&lt;Boolean&gt;()
// Variable for Id = cardViewTwo
val cardOptionTwoChecked = MutableLiveData&lt;Boolean&gt;()
}

CardViewAdapter.kt

@BindingAdapter(&quot;state_checked&quot;)
fun setStateChecked(view: MaterialCardView, liveData: MutableLiveData&lt;Boolean&gt;) {
if (view.isChecked != liveData.value) {
liveData.value = view.isChecked
}
}
@InverseBindingAdapter(attribute = &quot;state_checked&quot;)
fun getStateChecked(view: MaterialCardView,): Boolean {
return view.isChecked
}
// I don&#39;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(&quot;state_checkedAttrChanged&quot;)
fun setCheckedAttrListener(
view: MaterialCardView,
attrChange: InverseBindingListener,
) {
view.apply {
setOnClickListener { view.isChecked = true }
setOnCheckedChangeListener { card, isChecked -&gt;
if (card.isChecked &amp;&amp; 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(&quot;state_checked&quot;)
fun setStateChecked(view: MaterialCardView, liveData: MutableLiveData&lt;Boolean&gt;) {
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

&lt;layout
xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;&gt;
&lt;data&gt;
&lt;variable
name=&quot;vm&quot;
type=&quot;com.example.app.data.viewmodel.EmailViewModel&quot; /&gt;
&lt;/data&gt;
&lt;com.google.android.material.card.MaterialCardView
android:id=&quot;@+id/cardViewOne&quot;
android:checkable=&quot;true&quot;
android:clickable=&quot;true&quot;
android:focusable=&quot;true&quot;
&lt;!-- Deleted &quot;=&quot; --&gt;
app:state_checked=&quot;@{vm.cardOptionOneChecked}&quot;
&lt;/com.google.android.material.card.MaterialCardView&gt;
&lt;com.google.android.material.card.MaterialCardView
android:id=&quot;@+id/cardViewTwo&quot;
android:checkable=&quot;true&quot;
android:clickable=&quot;true&quot;
android:focusable=&quot;true&quot;
&lt;!-- Deleted &quot;=&quot; --&gt;
app:state_checked=&quot;@{vm.cardOptionTwoChecked}&quot;&gt;
&lt;/com.google.android.material.card.MaterialCardView&gt;
&lt;/layout&gt;

Third, Change viewmodel

class EmailViewModel @ViewModelInject constructor(
@ApplicationContext context: Context,
@Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val cardOptionOneChecked = MutableLiveData&lt;Boolean&gt;()
val cardOptionTwoChecked = MutableLiveData&lt;Boolean&gt;()
// 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.

huangapple
  • 本文由 发表于 2020年8月25日 03:54:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/63567889.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定