本文将介绍以下databinding的基本使用方式,主要包括数据的绑定,事件的监听,自定义监听方法等
介绍
DataBinding 是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM 模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常
一、使用
1、在 app module 的 build.gradle 中添加如下代码:
android {
dataBinding {
enabled = true
}
}
2、xml布局中生成 DataBinding 需要的布局规则
在布局的根目录快捷键Alt+Enter,点击 “Convert to data binding layout”即可生成如下符合规则的代码
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<LinearLayout
android:layout_margin="20dp"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:id="@+id/button"
android:text="点击"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
我们知道使用当前的button控件,通常在activity中都是通过findViewById的方式,这样代码就会显得特别的臃肿。
databingding和kotlin一样,最大的不一样就是可以直接通过id找到控件。
上面的例子我们已经生成了一个符合DataBinding规则的一个布局文件,那么此时系统就会默认帮我们生成了一个viewBinding的类。
public class TestActivity extends AppCompatActivity {
private AcitivityTestBinding mBinding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int layoutId = R.layout.acitivity_test;
mBinding = DataBindingUtil.setContentView(this, layoutId);
mBinding.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
}
可以看到我们定义了一个Binding,他的命名规则是 布局文件名字+Binding 。例如activity_test.xml对应的就是ActivityTestBinding 。 可以看到使用控件直接调用mBinding.button
事件监听
我们知道事件监听包括点击、长按等,在activity中代码通常需要写上一大段的回调,而DataBinding却为我们提供了很好的方式,网上有很多文章关于事件监听的,但是经过测试很多都会出错或者不能用。下面我们好好总结下。
首先定义的方法和事件匿名内部类中定义的返回类型必须相同,比如点击长按事件
mBinding.button.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});
对于DataBinding的事件监听的写法我们可以写一个内部类,然后定义类中的方法。针对上面的长按事件首先根据规则定义方法
public class OnEventListener{
public boolean onLongClick(){
Log.d(TAG, "onLongClick: ");
return true;
}
}
对应的xml布局文件中的定义
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="listener"
type="com.yaxon.yxproj.bean.TestActivity.OnEventListener" />
</data>
<LinearLayout
android:layout_margin="20dp"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:id="@+id/button"
android:text="点击"
android:onLongClick="@{()->listener.onLongClick()}"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
注意必须引入OnEventListener。
事件监听有了,接着记得绑定上内部类。因为我们在布局中生命了listener。所以databinding就会自动生成一个设置的方法
mBinding.setListener(new OnEventListener());
这句话就相当于原始绑定监听事件的代码,不同的是它可以同时把所有内部类中定义的方法都绑定,比起源生代码又减少了一部分。
那么有人会问,onLongClick(View view)参数列表中的view呢? 如果我需要使用呢?或者我想另外添加一个参数又要怎么定义呢?
其实事件绑定本人总结了三种的定义的方式:第一种不带任何参数(参考以上写法)、第二种带上原始的参数、第三种附加自定义参数
对于第一种写法请参考以上代码
第二种带原始参数:
public class OnEventListener{
public boolean onLongClick(View view){
Log.d(TAG, "onLongClick: ");
return true;
}
}
对应的xml布局
<Button
android:layout_width="wrap_content"
android:id="@+id/button"
android:text="点击"
android:onLongClick="@{listener::onLongClick}"
android:layout_height="wrap_content"/>
第三种写法带上自定义参数:
分为两种:
1、直接添加额外的参数
public class OnEventListener {
public boolean onLongClick(String url) {
Log.d(TAG, "onLongClick: "+url);
return true;
}
}
对应的xml布局
<Button
android:layout_width="wrap_content"
android:id="@+id/button"
android:text="点击"
android:onLongClick="@{()->listener.onLongClick(url)}"
android:layout_height="wrap_content"/>
2、添加原始参数+自定义参数
public class OnEventListener {
public boolean onLongClick(View view,String url) {
Log.d(TAG, "onLongClick: "+view.getId()+url);
return true;
}
}
对应的xml布局
<Button
android:layout_width="wrap_content"
android:id="@+id/button"
android:text="点击"
android:onLongClick="@{(view)->listener.onLongClick(view,url)}"
android:layout_height="wrap_content"/>
其他的事件监听只要遵循以上的规则即可,例如checkbox的事件监听
public void onCheck(CompoundButton buttonView, boolean isChecked) {
Log.d(TAG, "onCheck: " + isChecked);
}
<CheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="73dp"
android:layout_marginLeft="73dp"
android:layout_marginTop="31dp"
android:text="CheckBox"
android:onCheckedChanged="@{click::onCheck}"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toBottomOf="@+id/button4" />
当然写法可能有很多种例如网上还出现其他的写法可以运行,但是以上写法基本满足我们日常的开发。
数据绑定
通常情况下想要设置一个TextView的值需要写类似的代码 tv.setText("content");
DataBinding提供了数据和布局绑定的功能。例如我们可以申明一个ViewModel,以LoginViewModel作为例子
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="model"
type="com.yaxon.yxproj.model.LoginViewModel" />
<variable
name="listener"
type="com.yaxon.yxproj.bean.TestActivity.OnEventListener" />
</data>
<LinearLayout
android:layout_margin="20dp"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:id="@+id/button"
android:text="登录"
android:onLongClick="@{()->listener.onLoginClick()}"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:text="@={model.respondData}"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
可以看到 TextView的值已经绑定了model.respondData,一旦定义了一个变量相应的DataBinding就可以设置属性,例如我们设置了listener,DataBinding就有了setListener,定义了一个model它就有了setModel的方法。
public class TestActivity extends AppCompatActivity {
private static final String TAG = TestActivity.class.getSimpleName();
private AcitivityTestBinding mBinding;
private LoginViewModel loginViewModel ;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int layoutId = R.layout.acitivity_test;
mBinding = DataBindingUtil.setContentView(this, layoutId);
mBinding.setListener(new OnEventListener());
//这句话不设置则无法监听
mBinding.setLifecycleOwner(this);
loginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
mBinding.setModel(loginViewModel);
}
public class OnEventListener {
public boolean onLongClick(View view,String url) {
Log.d(TAG, "onLongClick: "+view.getId()+url);
return true;
}
public void onLoginClick(){
loginViewModel.doLogin("xxx","xxx");
}
}
}
一旦TextView绑定了model中respondData的值,那么TextView就会和该值绑定双向绑定,一方改变另一方必然改变。
当调用doLogin接口,respondData的值会被设置那么TextView显示的值也会被改变。
//LoginViewModel.class (doLogin)
public void doLogin(final String userName, final String paw) {
Observable<LoginRespondBean> observable = Observable.create(
new ObservableOnSubscribe<LoginRespondBean>() {
@Override
public void subscribe(ObservableEmitter<LoginRespondBean> emitter) {
Log.d("TAG", "subscribe:" + Thread.currentThread().getName());
LoginRespondBean login = ControlApi.getInstance().login(userName, paw);
emitter.onNext(login);
}
});
//切换到io异步线程执行耗时操作,并将结果返回到主线程
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<LoginRespondBean>() {
@Override
public void accept(LoginRespondBean loginRespondBean) {
Log.d("TAG", "accept:" + Thread.currentThread().getName());
respondData.setValue(loginRespondBean);
}
});
}
这里我们使用了LiveData配合DataBinding的使用。完整的代码如下
public class LoginViewModel extends ViewModel {
public MutableLiveData<LoginRespondBean> respondData = new MutableLiveData<>();
public MutableLiveData<String> registerData = new MutableLiveData<>();
/**
* 需要关心结果
*
* @param userName
* @param paw
*/
public void doLogin(final String userName, final String paw) {
Observable<LoginRespondBean> observable = Observable.create(
new ObservableOnSubscribe<LoginRespondBean>() {
@Override
public void subscribe(ObservableEmitter<LoginRespondBean> emitter) {
Log.d("TAG", "subscribe:" + Thread.currentThread().getName());
LoginRespondBean login = ControlApi.getInstance().login(userName, paw);
emitter.onNext(login);
}
});
//切换到io异步线程执行耗时操作,并将结果返回到主线程
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<LoginRespondBean>() {
@Override
public void accept(LoginRespondBean loginRespondBean) {
Log.d("TAG", "accept:" + Thread.currentThread().getName());
respondData.setValue(loginRespondBean);
}
});
}
}
目前支持双向绑定的参数列表如下:
自定义参数绑定:BindingAdapter
最常见的例子就是一个ImageView需要设置图片
一般的是写法是 获取到图片的url后借助Glide设置编写java代码。 同样的DataBinding也提供了一种自定义参数绑定的方式BindingAdapter
同样在ViewModel中定义一个方法:
public class DriverViewModel extends ViewModel {
@BindingAdapter({"imageUrl"})
public static void loadImage(ImageView imageView, String url){
Glide.with(imageView.getContext()).load(url)
.placeholder(R.mipmap.ic_launcher)
.into(imageView);
}
}
"imageUrl"表示xml控件中设置的属性名
参数列表中第一个必须是View或者子View的类型,第二个参数是自定义参数。
<ImageView
android:id="@+id/imageView"
imageUrl="@{url}"
android:layout_width="164dp"
android:layout_height="156dp"
android:layout_marginStart="53dp"
android:layout_marginLeft="53dp"
android:layout_marginTop="38dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
布局中设置了一个url,属于String类型。不管引入什么对象类型的变量都需要申明,
<data>
<variable
name="url"
type="String" />
<variable
name="model"
type="com.yaxon.yxproj.model.DriverViewModel" />
<variable
name="click"
type="com.yaxon.yxproj.DriverInfoActivity.EventListener" />
</data>
同样的道理我们的DataBinding就多了一个setUrl的方法,只要将正常的url地址设置进去就可以完成图片加载了。
未完待续...
网友评论