Android自定义视图:如何通过LiveData和数据绑定更新自定义枚举属性

时间:2019-05-05 08:51:10

标签: android kotlin enums android-databinding android-livedata

我正在构建一个自定义视图,该视图具有一些自定义属性,其中两个是枚举。我正在使用数据绑定和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 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中。有其他方法可以做到这一点吗?或者,如果我的方法很好,如何使它起作用? 谁可以提供帮助?

2 个答案:

答案 0 :(得分:0)

这可能不是一个真正的答案,但是尽管如此,我还是解决了我的问题:我意识到我可以在res/values/integers.xml中定义自定义整数,因此我摆脱了枚举并切换为整数可以在布局文件和Java / Kotlin类中轻松访问。 This对另一个问题的回答很好地总结了它是如何工作的。然后可以将这些整数轻松地输入到LiveData并更新视图。

答案 1 :(得分:0)

您可以尝试向WifiIconComponent添加一个setter函数:

public fun setWifiState ( state : WifiState ) { wifeState = state }

看起来有点骇人听闻。

我遇到了同样的问题,最后我以一个命名约定解决了这个问题:我的自定义视图中的属性称为Mode,它具有一个自定义的设置器:

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之类的东西,并且似乎选择了自定义设置器。