如何为 Android 上的 EditText 数据绑定到 onTextChanged?

How to databind to onTextChanged for an EditText on Android?

Yigit Boyar and George Mount's talk on Android Databinding 中,他们说明了绑定到 TextWatcheronTextChanged(在 13:41)是多么容易。在按钮上。他们的幻灯片错了吗?首先,Button 视图没有 onTextChanged 属性。它也没有 setOnTextChanged 方法。 EditText 也没有。但他们都有 addTextChangedListener 需要 TextWatcher 作为输入。

那么他们在说什么?他们是怎么做到的呢?他们的示例代码无法编译,但会出现此错误:

Error:(17) No resource identifier found for attribute 'onTextChanged' in package 'android'

如何使用 Android 数据绑定框架绑定到任何视图上的 "Text Changed Event",尤其是 EditText?

setOnFocusChangeListener附加到EditText,并使用将文本内容与全局变量(字段的前一个state/content)进行比较以确定它是否已更改:

    mEditTextTitle.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if(!hasFocus)
                // check if text has changed       
        }
    });

实际上它开箱即用。我认为我的错误是使用了旧版本的数据绑定框架。使用最新的,这是程序:

查看:

<EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/username"
    android:text="Enter username:"
    android:onTextChanged="@{data.onTextChanged}" />

型号:

public void onTextChanged(CharSequence s, int start, int before, int count) {
    Log.w("tag", "onTextChanged " + s);
}

还要确保您已将 model 分配给 DataBinding

例如。在你的 activity

lateinit var activityMainDataBinding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    activityMainDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) 
    val dataViewModel = ViewModelProvider(this).get(DataViewModel::class.java)
    activityMainDataBinding.dataModel = dataViewModel
}

确保您引用的是 gradle 构建工具 v1.5.0 或更高版本,并已在您的 build.gradle.

中使用 android.dataBinding.enabled true 启用数据绑定

编辑: 功能演示项目 here. view. model

为了扩展@Nilzors 的答案,也可以在布局中传递文本 and/or 其他参数:

查看:

<EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/username"
    android:text="Enter username:"
    android:onTextChanged="@{(text, start, before, count) -> viewModel.onUsernameTextChanged(text)}" />

ViewModel:

public void onUsernameTextChanged(CharSequence text) {
    // TODO do something with text
}

您始终需要传递零个或所有参数。

简单的方法

如果您使用 onTextChange() 更新模型中的文本 那么您可以直接使用

<data>
    <variable
        name="user"
        type="com.package.User"/>
</data>

<EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@={user.name}"/>

模型 class 将有 gettersetter:

public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

现在,模型中的 name 将随着用户交互而实时更改。所以使用 binding.getUser().getName() 将得到最新的文本。

单向绑定 只会在模型值更改时更新。它不会实时更新模型。

android:text="@{user.name}"

双向绑定 根据用户输入实时更新模型变量。

android:text="@={user.name}"

唯一的区别=(等号)receives data changes to the property and listen to user updates at the same time

最好的方法是添加绑定适配器和文本观察器。

public class Model{
    private TextWatcher textWatcher;

public Model(){
        this.textWatcher= getTextWatcherIns();
}

private TextWatcher getTextWatcherIns() {
        return new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                //do some thing
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                //do some thing

            }

            @Override
            public void afterTextChanged(Editable s) {
                //do some thing
            }
        };
    }

    public TextWatcher getTextWatcher() {
        return textWatcher;
    }

    public void setTextWatcher(TextWatcher textWatcher) {
        this.textWatcher = textWatcher;
    }

    @BindingAdapter("textChangedListener")
    public static void bindTextWatcher(EditText editText, TextWatcher textWatcher) {
        editText.addTextChangedListener(textWatcher);
    }
}

并在您的 xml 中将此属性添加到您的编辑文本中

<EditText
            android:id="@+id/et"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:textChangedListener="@{model.textWatcher}" />
  1. 创建一个class(我将他命名为BindingAdapters)。然后定义您的 bindingAdapter 方法。

    @BindingAdapter("app:textChangedListener")
    fun onTextChanged(et: EditText, number: Int) {
    et.addTextChangedListener(object : TextWatcher {
        override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
            if (et.text.toString().trim().length >= number) {
                et.setBackgroundColor(Color.GREEN)
            }
        }
    
        override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
        override fun afterTextChanged(s: Editable) {}
    })
    

    }

  2. 为 xml 布局中的 editText 设置此属性

    <EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:textChangedListener="@{3}" />  
    

如果文本更改后只需要 text 参数,则可以使用 android:afterTextChanged 绑定适配器。例如:

android:afterTextChanged="@{(text) -> viewModel.onTextChange(text)}"

然后在你的ViewModel中像这样实现:

fun onTextChange(editable: Editable?) {
    Log.d("TAG","New text: ${editable.toString()}")
}

此外,还有android:beforeTextChanged用于在文本更改事件之前识别旧文本,用法与android:afterTextChanged相同。

我是这样工作的:

片段:

    class DiAtomicMoleculesFragment : Fragment() {
        private lateinit var binding: FragmentDiatomicMoleculesBinding
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            binding = FragmentDiatomicMoleculesBinding.inflate(layoutInflater, container, false)
            return binding.root
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            val recyclerAdapter = DiAtomicMoleculesAdapter(onClickListener)
    
            binding.diRecyclerView.apply {
                setHasFixedSize(true)
                layoutManager = LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
                adapter = recyclerAdapter
            }
    
            val viewModel = ViewModelProvider(this).get(DiAtomicMoleculesViewModel::class.java)
            viewModel.getDiAtomicMoleculesByName().observe(viewLifecycleOwner, Observer { items ->
                recyclerAdapter.setData(items)
            })
    
            //this is important !!!!!!!!!!!!!
            binding.viewModel = viewModel
        }
    }

layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<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.mychemistry.viewmodel.DiAtomicMoleculesViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white">

        <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/di_search_box"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:gravity="center_vertical"
            android:hint="@string/search"
            android:paddingStart="10dp"
            android:paddingEnd="5dp"
            android:singleLine="true"
            android:textColor="@android:color/black"
            android:textColorHint="@android:color/darker_gray"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:onTextChanged="@{(text, start, before, count) -> viewModel.onTextChange(text)}"/>

<!--     or this ->      android:afterTextChanged="@{(e) -> viewModel.onTextChange(e)}"/>-->

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/di_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/di_search_box" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

视图模型:

class DiAtomicMoleculesViewModel : ViewModel() {
    private val allLiveData = AppDatabase.getInstance().getDiAtomicMoleculeDao().getAll()
    private val filterLiveData = MutableLiveData<String>()
    private val searchByLiveData = Transformations.switchMap(filterLiveData, ::filter)

    fun getDiAtomicMolecules(): LiveData<List<DiAtomicMolecule>> {
        return allLiveData
    }

    private fun filter(text: String): LiveData<List<DiAtomicMolecule>> {
        return AppDatabase.getInstance().getDiAtomicMoleculeDao().find(text)
    }

    fun getDiAtomicMoleculesByName(): LiveData<List<DiAtomicMolecule>> {
        return searchByLiveData
    }

    fun onTextChange(e: Editable?) {
        filterLiveData.value = e?.toString()
    }

    fun onTextChange(text: CharSequence?) {
        filterLiveData.value = text?.toString()
    }
}

我正在使用此方法处理 android 数据绑定中的文本更改侦听器。1st 在您的 ViewModel class 中创建 LiveData 变量,并创建 getText 方法,其中 returns LiveData 对象。

  • public MutableLiveData<String> verifyCodes = new MutableLiveData<>();
  • public LiveData<String> getCodes(){ return verifyCodes; }

然后在你的 xml 文件中 editText 字段设置属性 text 与上面的 liveData 字段绑定

  • <EditText android:id="@+id/et_verification1st" android:layout_width="match_parent" android:layout_height="match_parent" android:text="@={viewModel.verifyCodes}"/>

在数据绑定中,您已经知道如何在数据标记 i beleive.As 示例

中创建 viewModel class 的变量
  • <data> <variable name="viewModel" type="viewModel.VerifyUserActivityViewModel" /> </data>

好的,现在你的 activity 你必须 observe 我们在 viewModel class[= 中创建的 liveData 对象15=]

  • mViewModel.getCodes().observe(this,new Observer< String>(){ @Override public void onChange(String strings){ log.d("OnChange",strings); }});

onChange 方法

中更改文本时,您可以执行任何逻辑

1.在你的 BindingAdapter class 中,写下这个。在这里我已经传递了 viewModel 以便我们可以针对特定的 viewModel 执行特定的任务:

@BindingAdapter("app:addTextChangeListener")
fun addTextChangeListener(view: EditText, viewModel: ViewModel) {

    view.addTextChangedListener(object : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        }

        override fun afterTextChanged(s: Editable?) {
            when (viewModel) {
                is LoginViewModel -> viewModel.invisibleErrorTexts()
            }
        }

    })

}

2。在您的 XML 中,在 Edittext 中,放置下面给出的属性: 这里“viewModel”是我布局标签中LoginViewModel的变量名

app:addTextChangeListener="@{viewModel}"