如何使用数据绑定设置点击侦听器并将编辑文本字段值传递给视图模型
How to set click listener and pass edittext fields value to view model using data binding
您好,我正在尝试在我的 android 应用程序中使用数据绑定和 mvvm 架构。我想在布局中使用数据绑定添加点击侦听器,并将用户名和密码 edittext
的值发送到视图模型,它将执行 Web 服务并调用 LoginActivity
的适当方法,如 [=15] =].
有谁知道该怎么做还是我采取了错误的方法?我有以下 activity、布局和视图模型
的代码片段
LoginActivity.kt
class LoginActivity : BaseActivity(), LoginNavigator {
@Inject
lateinit var loginViewModel: LoginActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val activityLoginBinding = DataBindingUtil.setContentView<ActivityLoginBinding>(this, R.layout.activity_login)
}
override fun startHomeActivity() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun startRegistrationActivity() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun startForgotPasswordActivity() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun handleError(throwable: Throwable) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
LoginActivityViewModel.kt
class LoginActivityViewModel {
fun login(email: String, password: String) {
}
/**
* Validate email and password. It checks email and password is empty or not
* and validate email address is correct or not
* @param email email address for login
* @param password password for login
* @return true if email and password pass all conditions else false
*/
fun isEmailAndPasswordValid(email: String, password: String): Boolean {
if (email.isEmpty()) return false
if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) return false
if (password.isEmpty()) return false
return true
}
}
activity_login.xml
<layout>
<ScrollView 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
tools:context="com.app.android.login.LoginActivity"
tools:ignore="missingPrefix">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/default_view_margin_bottom_8dp">
<android.support.design.widget.TextInputLayout
android:id="@+id/til_login_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
android:layout_marginStart="@dimen/default_view_margin_left_8dp"
android:textColorHint="@color/colorSecondaryText"
app:hintTextAppearance="@style/AppTheme.InputLayoutStyle"
app:layout_constraintBottom_toTopOf="@+id/til_login_password"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_email"
android:imeOptions="actionNext"
android:singleLine="true"
android:textColor="@color/colorPrimaryText" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/til_login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
android:layout_marginStart="@dimen/default_view_margin_left_8dp"
android:textColorHint="@color/colorSecondaryText"
app:hintTextAppearance="@style/AppTheme.InputLayoutStyle"
app:layout_constraintBottom_toTopOf="@+id/btn_login_login"
app:layout_constraintTop_toBottomOf="@+id/til_login_email"
app:layout_constraintVertical_chainStyle="packed">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_password"
android:imeOptions="actionDone"
android:singleLine="true"
android:textColor="@color/colorPrimaryText" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/btn_login_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
android:layout_marginStart="@dimen/default_view_margin_left_8dp"
android:layout_marginTop="48dp"
android:text="@string/login_btn_text"
android:textColor="@color/colorWhite"
app:layout_constraintBottom_toTopOf="@+id/textview_login_forgot_password"
app:layout_constraintTop_toBottomOf="@+id/til_login_password"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/textview_login_forgot_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
android:layout_marginStart="@dimen/default_view_margin_left_8dp"
android:layout_marginTop="36dp"
android:gravity="center"
android:text="@string/login_forgot_password"
app:layout_constraintBottom_toTopOf="@+id/btn_login_register"
app:layout_constraintTop_toBottomOf="@+id/btn_login_login"
app:layout_constraintVertical_chainStyle="packed" />
<Button
android:id="@+id/btn_login_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
android:layout_marginStart="@dimen/default_view_margin_left_8dp"
android:text="@string/login_sign_up"
android:textColor="@color/colorWhite"
app:layout_constraintBottom_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout>
</ScrollView>
</layout>
首先重命名您的 ViewModel。它由 View 分隔,这意味着名称应该类似于 LoginViewModel。对于此尝试(这是在 android 中使用 mvvm 模式的最佳方法),您需要 AAC/LiveData.
其次,您应该进行双向数据绑定并将 ViewModel 分配给您的布局。
<?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="...YourVm" />
</data>
<android.support.design.widget.TextInputEditText ...
android:text="@={viewModel.yourField}" />
<Button ... onClick="@{viewModel.onClick}" />
</layout>
这需要在您的 ViewModel 中使用 ObservableField<String>
。
现在您想要通过在 activity 中传递点击事件来验证点击是否发生。对于这种情况,您在 ViewModel 中创建 Listener 并将数据传递给 Observable。
class LoginViewModel {
val yourField = ObservableField<String>()
val uiEventLiveData = SingleLiveData<Int>()
fun onClick(view:View) {
uiObservable.data = 1 // or any other event
}
}
在此之后,您可以使用 Activity 或 Fragment 来观察 UI 使用 LiveData(生命周期感知!)的事件。
现在您可以使用绑定到 ViewModel 的任何片段/Activity 来观察 UI 事件,例如:
class YourActivity {
private val yourvm by lazy { ViewModelProviders.of(this, viewModelFactory).get(Yourvm::class.java) }
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// ....
binding.viewModel = yourVm
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
yourVm.uiEventLiveData.observe(this, Observer {
when(it) {
1-> { doSomeLoginStuff(yourVm.yourField, ...) } //click happened, do something
else -> .... // unknown ui event
}
})
}
您需要 Class SingleLiveData,它是一个 MutableLiveData,但会在其发出时使您的数据无效。
class SingleLiveData<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
@MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
value = null
}
companion object {
private val TAG = "SingleLiveData"
}
}
有几次尝试使用 WeakReferences 来避免上下文泄漏,但我强烈建议不要这样做。原因是您想将逻辑与您的视图分开。拥有引用,即使它们是懒惰的或弱的也会破坏架构。
您好,我正在尝试在我的 android 应用程序中使用数据绑定和 mvvm 架构。我想在布局中使用数据绑定添加点击侦听器,并将用户名和密码 edittext
的值发送到视图模型,它将执行 Web 服务并调用 LoginActivity
的适当方法,如 [=15] =].
有谁知道该怎么做还是我采取了错误的方法?我有以下 activity、布局和视图模型
的代码片段LoginActivity.kt
class LoginActivity : BaseActivity(), LoginNavigator {
@Inject
lateinit var loginViewModel: LoginActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val activityLoginBinding = DataBindingUtil.setContentView<ActivityLoginBinding>(this, R.layout.activity_login)
}
override fun startHomeActivity() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun startRegistrationActivity() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun startForgotPasswordActivity() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun handleError(throwable: Throwable) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
LoginActivityViewModel.kt
class LoginActivityViewModel {
fun login(email: String, password: String) {
}
/**
* Validate email and password. It checks email and password is empty or not
* and validate email address is correct or not
* @param email email address for login
* @param password password for login
* @return true if email and password pass all conditions else false
*/
fun isEmailAndPasswordValid(email: String, password: String): Boolean {
if (email.isEmpty()) return false
if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) return false
if (password.isEmpty()) return false
return true
}
}
activity_login.xml
<layout>
<ScrollView 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
tools:context="com.app.android.login.LoginActivity"
tools:ignore="missingPrefix">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/default_view_margin_bottom_8dp">
<android.support.design.widget.TextInputLayout
android:id="@+id/til_login_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
android:layout_marginStart="@dimen/default_view_margin_left_8dp"
android:textColorHint="@color/colorSecondaryText"
app:hintTextAppearance="@style/AppTheme.InputLayoutStyle"
app:layout_constraintBottom_toTopOf="@+id/til_login_password"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_email"
android:imeOptions="actionNext"
android:singleLine="true"
android:textColor="@color/colorPrimaryText" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/til_login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
android:layout_marginStart="@dimen/default_view_margin_left_8dp"
android:textColorHint="@color/colorSecondaryText"
app:hintTextAppearance="@style/AppTheme.InputLayoutStyle"
app:layout_constraintBottom_toTopOf="@+id/btn_login_login"
app:layout_constraintTop_toBottomOf="@+id/til_login_email"
app:layout_constraintVertical_chainStyle="packed">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_password"
android:imeOptions="actionDone"
android:singleLine="true"
android:textColor="@color/colorPrimaryText" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/btn_login_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
android:layout_marginStart="@dimen/default_view_margin_left_8dp"
android:layout_marginTop="48dp"
android:text="@string/login_btn_text"
android:textColor="@color/colorWhite"
app:layout_constraintBottom_toTopOf="@+id/textview_login_forgot_password"
app:layout_constraintTop_toBottomOf="@+id/til_login_password"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/textview_login_forgot_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
android:layout_marginStart="@dimen/default_view_margin_left_8dp"
android:layout_marginTop="36dp"
android:gravity="center"
android:text="@string/login_forgot_password"
app:layout_constraintBottom_toTopOf="@+id/btn_login_register"
app:layout_constraintTop_toBottomOf="@+id/btn_login_login"
app:layout_constraintVertical_chainStyle="packed" />
<Button
android:id="@+id/btn_login_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
android:layout_marginStart="@dimen/default_view_margin_left_8dp"
android:text="@string/login_sign_up"
android:textColor="@color/colorWhite"
app:layout_constraintBottom_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout>
</ScrollView>
</layout>
首先重命名您的 ViewModel。它由 View 分隔,这意味着名称应该类似于 LoginViewModel。对于此尝试(这是在 android 中使用 mvvm 模式的最佳方法),您需要 AAC/LiveData.
其次,您应该进行双向数据绑定并将 ViewModel 分配给您的布局。
<?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="...YourVm" />
</data>
<android.support.design.widget.TextInputEditText ...
android:text="@={viewModel.yourField}" />
<Button ... onClick="@{viewModel.onClick}" />
</layout>
这需要在您的 ViewModel 中使用 ObservableField<String>
。
现在您想要通过在 activity 中传递点击事件来验证点击是否发生。对于这种情况,您在 ViewModel 中创建 Listener 并将数据传递给 Observable。
class LoginViewModel {
val yourField = ObservableField<String>()
val uiEventLiveData = SingleLiveData<Int>()
fun onClick(view:View) {
uiObservable.data = 1 // or any other event
}
}
在此之后,您可以使用 Activity 或 Fragment 来观察 UI 使用 LiveData(生命周期感知!)的事件。
现在您可以使用绑定到 ViewModel 的任何片段/Activity 来观察 UI 事件,例如:
class YourActivity {
private val yourvm by lazy { ViewModelProviders.of(this, viewModelFactory).get(Yourvm::class.java) }
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// ....
binding.viewModel = yourVm
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
yourVm.uiEventLiveData.observe(this, Observer {
when(it) {
1-> { doSomeLoginStuff(yourVm.yourField, ...) } //click happened, do something
else -> .... // unknown ui event
}
})
}
您需要 Class SingleLiveData,它是一个 MutableLiveData,但会在其发出时使您的数据无效。
class SingleLiveData<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
@MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
value = null
}
companion object {
private val TAG = "SingleLiveData"
}
}
有几次尝试使用 WeakReferences 来避免上下文泄漏,但我强烈建议不要这样做。原因是您想将逻辑与您的视图分开。拥有引用,即使它们是懒惰的或弱的也会破坏架构。