如何在Android上将数据绑定到EditText的onTextChanged事件?

62
Yigit Boyar和George Mount关于Android数据绑定的演讲中,他们展示了如何在13:41时轻松绑定TextWatcheronTextChanged事件。在一个按钮上。他们的幻灯片有误吗?首先,Button视图没有onTextChanged属性。它也没有setOnTextChanged方法。EditText也是同样的情况。但它们都有一个接受TextWatcher作为输入的addTextChangedListener方法。
那么他们在说什么?他们是如何做到的?他们的示例代码无法编译,但会出现以下错误:
Error:(17) No resource identifier found for attribute 'onTextChanged' in package 'android'

如何使用Android Databinding框架绑定到任何视图或特定的EditText上的“文本更改事件”?

Yigit的视频链接只显示他们谈话的图像,你无法知道正在发生什么。 - Mitch
10个回答

110

实际上,它可以直接使用。我想我的错误是使用了旧版本的数据绑定框架。使用最新版本,以下是操作步骤:

视图:

<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);
}

还要确保您已将模型分配给DataBinding

例如,在您的活动中

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 Build Tools v1.5.0或更高版本,并使用android.dataBinding.enabled true在您的build.grandle文件中启用数据绑定。

编辑:功能演示项目在此处查看模型


19
这个解决方案无效。EditText没有onTextChanged属性。 - M. Palsich
4
@M.Palsich 它就是有效的。https://news.realm.io/news/data-binding-android-boyar-mount/ - Sotti
2
是的,EditText不需要onTextChanged属性,因为它被定义为BindingAdapter:https://android.googlesource.com/platform/frameworks/data-binding/+/android-6.0.0_r7/extensions/baseAdapters/src/main/java/android/databinding/adapters/TextViewBindingAdapter.java#299。简而言之,数据绑定就是魔法。 - Nilzor
1
有没有办法在onTextChanged方法中对EditText进行操作?例如,假设我想更改文本颜色。 - sliders_alpha
3
不起作用,刚刚尝试过。不知道它是如何被接受的,也不知道你们中的任何人是如何使其工作的。 - clementiano
显示剩余5条评论

65

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

视图:

<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)}" />

视图模型:

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

你总是需要传递零个或所有参数。


4
无法运行!它显示onTextChanged不是一个属性。 - AnupamChugh
8
如果您已在项目中正确设置数据绑定,则已经准备好了一些绑定适配器,这是其中之一。请参见https://android.googlesource.com/platform/frameworks/data-binding/+/android-6.0.0_r7/extensions/baseAdapters/src/main/java/android/databinding/adapters/TextViewBindingAdapter.java#299,并查看@Nilzor答案下方的其他评论。 - Micer
3
如何编写 afterTextChange 方法的代码?我不想向该方法传递任何数据,我将从 LiveData 中获取文本。 - akshay bhange

44

简单方法

如果您正在使用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}"/>

这个模型类将拥有一个 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}"

唯一的区别在于=(等号)可以同时接收属性的数据变化并监听用户更新


这样做是行不通的,除非你能接受出现多余的回调。 - Mitch
1
当名称更改时,我该如何打印名称? - dawis11
按我所料,那就是这样。 - ugali soft
@Khemraj,但是当我开始在EditText中输入时,我如何立即获取数据? - Siddhpura Amit
这是官方解决方案。这应该被接受为答案。 - zihadrizkyef
显示剩余3条评论

12

如果在文本更改后只需要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相同。


6

我使用这种方法来处理Android DataBinding中的文本更改监听器。1,您需要在您的ViewModel类中创建LiveData变量,并创建一个getText方法,该方法返回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}"/>

在DataBinding中,您已经知道如何在data标记内创建viewModel类的变量,例如:

  • <data> <variable name="viewModel" type="viewModel.VerifyUserActivityViewModel" /> </data>

好了,现在在您的activity中,您需要观察我们在viewModel类中创建的liveData对象

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

当文本更改时,您可以在onChange方法中执行任何逻辑。


你的方法不是以暴露MutableLiveData给Activity/Fragment为代价吗? - MuM6oJuM6o

5

1. 在您的BindingAdapter类中,编写以下内容。在这里,我传递了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}"

3

I got it working like this:

Fragment:

    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()
    }
}

1
  1. create a class (I named him BindingAdapters). Then define your bindingAdapter methods.

    @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. set this attr for editText in xml layout

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

0

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

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}" />

java.lang.IllegalStateException: SpotFragmentBindingImpl 类中所需的 DataBindingComponent 为空。在 com.mytrain.findmytrain.ui.viewmodel.SpotViewmodel 中的 BindingAdapter 不是静态的,需要使用从 DataBindingComponent 检索到的对象。如果您不使用一个带有 DataBindingComponent 的膨胀方法,可以使用 DataBindingUtil.setDefaultComponent 或使所有 BindingAdapter 方法为静态。 - ABDUL RAHMAN
@ABDULRAHMAN,你的BindingAdapter方法不是静态的,我说得对吗? - mahdi shahbazi

-7
setOnFocusChangeListener附加到EditText上,并使用与全局变量(字段先前状态/内容)进行文本内容比较,以确定它是否已更改:
    mEditTextTitle.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if(!hasFocus)
                // check if text has changed       
        }
    });

至少,这是一种实现“onTextChanged”的方法,但这并不完全是你所要求的。我的解决方案仅在用户实际离开EditText时才触发。 - joakimk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接