首页 > 解决方案 > 具有非字符串原始类型的 Android (Kotlin) 双向数据绑定

问题描述

我正在尝试在 Android 中进行 2 向数据绑定(模拟运行 API 级别 29 的 Pixel 3a,最小 API 目标为 21)。我想@={}在 EditText 的android:text属性上使用语法。

文档似乎说我们可以使用静态转换器类来转换原始(或可能是复杂的)数据类型,使用注释来描述哪些方法与其他方法相反。我目前正在使用整数,但至少也会使用双打。

从 Int 到字符串的转换方法工作正常。当我的片段布局被膨胀时调用它(尽管奇怪的是,它立即被调用了两次),但是从字符串转换回 int 永远不会被调试器命中。

数据绑定转换器.kt

package com.razelon.svcman.database

import android.widget.EditText
import androidx.databinding.InverseMethod

object DatabindingConverters {

    @JvmStatic
    @InverseMethod("intToString")
    fun stringToInt(v: EditText, old: Int, new: String): Int {
        return new.toInt()
    }

    @JvmStatic
    fun intToString(v: EditText, old: Int, new: Int): String {
        return new.toString()
    }
}

fragment_new_order.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>
        <variable
            name="viewModel"
            type="com.razelon.svcman.orders.FragmentNewOrderViewModel" />

        <import type="com.razelon.svcman.database.DatabindingConverters" />
    </data>

    <ScrollView
        android:id="@+id/order_scroller"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbarStyle="outsideOverlay">


        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/constraint"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="48dp">

            <EditText
                android:id="@+id/edtWorkOrder"
                android:layout_width="0dp"
                android:layout_height="45dp"
                android:layout_marginStart="24dp"
                android:layout_marginEnd="16dp"
                android:ems="6"
                android:hint="@string/hint_work_order"
                android:importantForAutofill="no"
                android:inputType="number"

                // The line in question, for XML
                android:text="@={DatabindingConverters.intToString(edtWorkOrder, viewModel.orderNo, viewModel.orderNo)}"


                app:layout_constraintBaseline_toBaselineOf="@+id/edtAcct"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@+id/edtAcct" />


           // Other views

        </androidx.constraintlayout.widget.ConstraintLayout>

    </ScrollView>
</layout>

FragmentNewOrderViewModel.kt

package com.razelon.svcman.orders

import android.app.Application
import androidx.databinding.Bindable
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.razelon.svcman.database.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlin.math.max

const val MAX_WORK_CODES = 5

class FragmentNewOrderViewModel(
    val database: SvcDao,
    app: Application
) : AndroidViewModel(app) {

    private val job = Job()

    private val uiScope = CoroutineScope(Dispatchers.Main + job)

    private val _listWorkCodes = MutableLiveData<List<WorkCode>>(listOf())

    private val _listParts = MutableLiveData<List<SvcPart>>(listOf())

    private val _eventResetOrder = MutableLiveData<Boolean>(false)

    val serviceMen = database.getAllServiceMen()

    val listWorkCodes: LiveData<List<WorkCode>>
        get() = _listWorkCodes

    val listSvcParts: LiveData<List<SvcPart>>
        get() = _listParts

    val eventResetOrder: LiveData<Boolean>
        get() = _eventResetOrder

    val orderNo = MutableLiveData<Int>(0)



    fun addWorkCode(): Boolean {
        if(_listWorkCodes.value!!.size >= MAX_WORK_CODES) {
            return false
        }
        _listWorkCodes.value = List<WorkCode>(_listWorkCodes.value!!.size + 1) {
            if (it < (_listWorkCodes.value!!.size)) {
                _listWorkCodes.value?.get(it)!!
            } else {
                WorkCode(0)
            }
        }
        return true
    }

    fun updateWorkCode(code: WorkCode) {

    }

    fun deleteWorkCode(pos: Int) {
        _listWorkCodes.value = List<WorkCode>(max(_listWorkCodes.value!!.size - 1, 0)) {
            if(it >= pos) _listWorkCodes.value!![it + 1] else _listWorkCodes.value!![it]
        }
    }

    fun addNewPart() {
        _listParts.value = List<SvcPart>(_listParts.value!!.size + 1) {
            if(it < _listParts.value!!.size) {
                _listParts.value!![it]
            } else {
                SvcPart("", 0.0, 1)
            }
        }
    }

    fun deletePart(pos: Int) {
        _listParts.value = List<SvcPart>(max(_listParts.value!!.size - 1, 0)) {
            if(it >= pos) _listParts.value!![it + 1] else _listParts.value!![it]
        }
    }


    fun resetOrder() {
        _eventResetOrder.value = true
        _listParts.value = listOf()
        _listWorkCodes.value = listOf()
    }

    fun onOrderReset() {
        _eventResetOrder.value = false
    }
}

我不知道为什么它不调用@InverseMethod("intToString")注释标记的逆变器。我object为类声明使用了关键字,用所需的注释标记了方法,将类型导入到我的布局数据绑定中,并让方法中的参数与文档中的内容相匹配。我是 Kotlin 和 Android 开发的新手,所以我确信它可能是我很容易忽略的东西,但是对于我在这里缺少的东西,任何见解都会受到赞赏。

标签: javaandroidkotlindata-binding

解决方案


我想通了,这完全是我的错。它没有在帖子中显示,但在我的片段中,我从未真正将布局中的变量分配给我的绑定,因此没有任何东西可以将值发送回。

设置它为我修复了它,因为我之前忘记了它:

FragmentNewOrder.kt

        val app = this.activity!!.application
        val ds = SvcDatabase.getInstance(app).DAO
        val factory = FragmentNewOrderViewModelFactory(ds, app)
        val viewModel =
            ViewModelProviders.of(this, factory).get(FragmentNewOrderViewModel::class.java)

        // Very important, don't forget to bind your data 
        binding.viewModel = viewModel

推荐阅读