Android 自定义视图:如何通过 LiveData 和数据绑定更新自定义枚举属性
Android custom view: How to update custom enum attribute via LiveData & data binding
我正在构建一个自定义视图,它有一些自定义属性,其中两个是枚举。我正在使用数据绑定和 MutableLiveData 来更新我的视图。这没有问题,但我无法让它与我的枚举一起使用。有人可以帮忙吗?
所有代码示例都已精简,仅显示与问题相关的内容。如果缺少重要的东西,请指出。
这是我的布局:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<data>
<variable
name="viewModel"
type="com.myapp.wifi.WifiItemViewModel"
/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/margin_16"
android:paddingBottom="@dimen/margin_16"
>
<components.wifi.WifiIconComponent
android:id="@+id/img_wifi_icon"
style="@style/WrapContent"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="8dp"
app:wifiState="@{viewModel.wifiState}"
app:termsAndConditionsAccepted="@{viewModel.termsAccepted}"
app:layout_constraintEnd_toEndOf="@id/guideline_start"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="@+id/text_wifi_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textAppearance="@style/H5"
android:text="@{viewModel.wifiName}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_start"
app:layout_constraintEnd_toEndOf="@id/guideline_end"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
我的自定义视图是 WifiIconComponent
。这就是我在 attrs.xml
:
中声明其自定义属性的方式
<declare-styleable name="WifiIconComponent">
<attr name="wifiState" format="enum">
<enum name="NotAdded" value="0"/>
<enum name="NotAvailable" value="1"/>
<enum name="Available" value="2"/>
<enum name="ConnectedWithoutInternet" value="3"/>
<enum name="Connected" value="4"/>
</attr>
<attr name="termsAndConditionsAccepted" format="boolean"/>
<attr name="iconSizing" format="enum">
<enum name="small" value="0"/>
<enum name="medium" value="1"/>
<enum name="large" value="2"/>
</attr>
</declare-styleable>
那是视图的 class 文件:
class WifiIconComponent @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ImageView(context, attrs, defStyleAttr) {
private var wifiState = WifiState.NOT_ADDED
private var termsAndConditionsAccepted = false
private var iconSize = WifiIconSize.SMALL
init {
attrs?.let {
val attributes = context.obtainStyledAttributes(it, R.styleable.WifiIconComponent)
termsAndConditionsAccepted =
attributes.getBoolean(R.styleable.WifiIconComponent_termsAndConditionsAccepted, false)
iconSize = WifiIconSize.values()[attributes.getInt(R.styleable.WifiIconComponent_iconSizing, 0)]
wifiState = WifiState.values()[attributes.getInt(R.styleable.WifiIconComponent_wifiState, 0)]
attributes.recycle()
}
setImageDrawable(getStateIcon())
}
private fun getStateIcon(): Drawable? {
val resource = when (iconSize) {
WifiIconSize.SMALL -> getSmallIcon()
WifiIconSize.MEDIUM -> getMediumIcon()
WifiIconSize.LARGE -> getLargeIcon()
}
return context.getDrawable(resource)
}
// rest of class
}
这是布局中引用的 viewModel:
class WifiItemViewModel(state: WifiConnectionState, val termsAccepted: Boolean) {
val description = dummyArg.description
var wifiState = MutableLiveData(state.wifiState)
val wifiStateAsString = MutableLiveData(wifiState.value.toString())
val wifiName = MutableLiveData(state.name)
val switchVisibility = MutableLiveData(View.GONE)
val activationState = MutableLiveData(state.isActivated)
// the rest of the class is unrelated to the problem
}
现在,如果我只是在布局中设置任何我想要的值,比如 app:wifiState="Connected"
,一切都可以正常工作。在 init 块中,设置值被正确地转换为我在代码中拥有的枚举的正确状态:
enum class WifiState {
NOT_ADDED,
NOT_AVAILABLE,
AVAILABLE,
CONNECTED_WITHOUT_INTERNET,
CONNECTED_WITH_INTERNET
}
但是如果我将它绑定到我的 viewModel 中的 MutableLiveData(就像我在上面的布局中所做的那样),我会收到此错误:
****/ data binding error ****msg:Cannot find the setter for attribute 'app:wifiState' with parameter type androidx.lifecycle.MutableLiveData<components.wifi.WifiState> on components.wifi.WifiIconComponent.
我认为这与具有相同值的两个不同枚举基本有关,一个在代码中,一个在 xml 中。有没有不同的方法来做到这一点?或者,如果我的方法没问题,我该如何让它发挥作用?
谁能帮忙?
这可能不是一个真正的答案,但它是我解决问题的方法:我意识到我可以在 res/values/integers.xml
中定义自定义整数,所以我只是摆脱了我的枚举并切换到可以在布局文件和 Java/Kotlin 类 中轻松访问的整数。
对另一个问题的回答很好地总结了它是如何工作的。然后可以轻松地将这些整数提供给 LiveData 并更新视图。
您可以尝试向 WifiIconComponent 添加一个 setter 函数:
public fun setWifiState ( state : WifiState ) { wifeState = state }
这看起来有点乱。
我遇到了同样的问题,最后解决了它,我想,命名约定:我自定义视图中的 属性 称为模式,它有一个自定义 setter:
class DisablableEditText
{
enum class EditMode { Disabled, Enabled }
var Mode = EditMode.Enabled
set( value : EditMode )
{
when (value)
{
EditMode.Enabled -> _enableEdit()
EditMode.Disabled -> _disableEdit()
}
field = value
}
...
我的attr.xml包含
<declare-styleable name="DisablableEditText">
<attr name="Mode" format="enum" >
<enum name="disabled" value="0"/>
<enum name="enabled" value="1"/>
</attr>
</declare-styleable>
xml 看起来像这样
<ps.psandroid.views.DisablableEditText
...
app:Mode="@{viewmodel.EditMode}" />
并且viewmodel.EditMode像这样
val EditMode : LiveData<DisablableEditText.EditMode> =
Transformations.map( _mode ) { value ->
when ( value )
{
Mode.VIEWING -> DisablableEditText.EditMode.Disabled
else -> DisablableEditText.EditMode.Enabled
}
}
所以属性名和我的一样属性。 DataBinding 寻找带有正确签名的 setMode
之类的东西,并且似乎选择了自定义的 setter.
我正在构建一个自定义视图,它有一些自定义属性,其中两个是枚举。我正在使用数据绑定和 MutableLiveData 来更新我的视图。这没有问题,但我无法让它与我的枚举一起使用。有人可以帮忙吗?
所有代码示例都已精简,仅显示与问题相关的内容。如果缺少重要的东西,请指出。 这是我的布局:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<data>
<variable
name="viewModel"
type="com.myapp.wifi.WifiItemViewModel"
/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/margin_16"
android:paddingBottom="@dimen/margin_16"
>
<components.wifi.WifiIconComponent
android:id="@+id/img_wifi_icon"
style="@style/WrapContent"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="8dp"
app:wifiState="@{viewModel.wifiState}"
app:termsAndConditionsAccepted="@{viewModel.termsAccepted}"
app:layout_constraintEnd_toEndOf="@id/guideline_start"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="@+id/text_wifi_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textAppearance="@style/H5"
android:text="@{viewModel.wifiName}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_start"
app:layout_constraintEnd_toEndOf="@id/guideline_end"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
我的自定义视图是 WifiIconComponent
。这就是我在 attrs.xml
:
<declare-styleable name="WifiIconComponent">
<attr name="wifiState" format="enum">
<enum name="NotAdded" value="0"/>
<enum name="NotAvailable" value="1"/>
<enum name="Available" value="2"/>
<enum name="ConnectedWithoutInternet" value="3"/>
<enum name="Connected" value="4"/>
</attr>
<attr name="termsAndConditionsAccepted" format="boolean"/>
<attr name="iconSizing" format="enum">
<enum name="small" value="0"/>
<enum name="medium" value="1"/>
<enum name="large" value="2"/>
</attr>
</declare-styleable>
那是视图的 class 文件:
class WifiIconComponent @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ImageView(context, attrs, defStyleAttr) {
private var wifiState = WifiState.NOT_ADDED
private var termsAndConditionsAccepted = false
private var iconSize = WifiIconSize.SMALL
init {
attrs?.let {
val attributes = context.obtainStyledAttributes(it, R.styleable.WifiIconComponent)
termsAndConditionsAccepted =
attributes.getBoolean(R.styleable.WifiIconComponent_termsAndConditionsAccepted, false)
iconSize = WifiIconSize.values()[attributes.getInt(R.styleable.WifiIconComponent_iconSizing, 0)]
wifiState = WifiState.values()[attributes.getInt(R.styleable.WifiIconComponent_wifiState, 0)]
attributes.recycle()
}
setImageDrawable(getStateIcon())
}
private fun getStateIcon(): Drawable? {
val resource = when (iconSize) {
WifiIconSize.SMALL -> getSmallIcon()
WifiIconSize.MEDIUM -> getMediumIcon()
WifiIconSize.LARGE -> getLargeIcon()
}
return context.getDrawable(resource)
}
// rest of class
}
这是布局中引用的 viewModel:
class WifiItemViewModel(state: WifiConnectionState, val termsAccepted: Boolean) {
val description = dummyArg.description
var wifiState = MutableLiveData(state.wifiState)
val wifiStateAsString = MutableLiveData(wifiState.value.toString())
val wifiName = MutableLiveData(state.name)
val switchVisibility = MutableLiveData(View.GONE)
val activationState = MutableLiveData(state.isActivated)
// the rest of the class is unrelated to the problem
}
现在,如果我只是在布局中设置任何我想要的值,比如 app:wifiState="Connected"
,一切都可以正常工作。在 init 块中,设置值被正确地转换为我在代码中拥有的枚举的正确状态:
enum class WifiState {
NOT_ADDED,
NOT_AVAILABLE,
AVAILABLE,
CONNECTED_WITHOUT_INTERNET,
CONNECTED_WITH_INTERNET
}
但是如果我将它绑定到我的 viewModel 中的 MutableLiveData(就像我在上面的布局中所做的那样),我会收到此错误:
****/ data binding error ****msg:Cannot find the setter for attribute 'app:wifiState' with parameter type androidx.lifecycle.MutableLiveData<components.wifi.WifiState> on components.wifi.WifiIconComponent.
我认为这与具有相同值的两个不同枚举基本有关,一个在代码中,一个在 xml 中。有没有不同的方法来做到这一点?或者,如果我的方法没问题,我该如何让它发挥作用? 谁能帮忙?
这可能不是一个真正的答案,但它是我解决问题的方法:我意识到我可以在 res/values/integers.xml
中定义自定义整数,所以我只是摆脱了我的枚举并切换到可以在布局文件和 Java/Kotlin 类 中轻松访问的整数。
您可以尝试向 WifiIconComponent 添加一个 setter 函数:
public fun setWifiState ( state : WifiState ) { wifeState = state }
这看起来有点乱。
我遇到了同样的问题,最后解决了它,我想,命名约定:我自定义视图中的 属性 称为模式,它有一个自定义 setter:
class DisablableEditText
{
enum class EditMode { Disabled, Enabled }
var Mode = EditMode.Enabled
set( value : EditMode )
{
when (value)
{
EditMode.Enabled -> _enableEdit()
EditMode.Disabled -> _disableEdit()
}
field = value
}
...
我的attr.xml包含
<declare-styleable name="DisablableEditText">
<attr name="Mode" format="enum" >
<enum name="disabled" value="0"/>
<enum name="enabled" value="1"/>
</attr>
</declare-styleable>
xml 看起来像这样
<ps.psandroid.views.DisablableEditText
...
app:Mode="@{viewmodel.EditMode}" />
并且viewmodel.EditMode像这样
val EditMode : LiveData<DisablableEditText.EditMode> =
Transformations.map( _mode ) { value ->
when ( value )
{
Mode.VIEWING -> DisablableEditText.EditMode.Disabled
else -> DisablableEditText.EditMode.Enabled
}
}
所以属性名和我的一样属性。 DataBinding 寻找带有正确签名的 setMode
之类的东西,并且似乎选择了自定义的 setter.