重构公司一个项目时候使用了MVVM + databinding来当主体架构,这种架构更加偏向于前端的风格,比以往的MVP更加轻量和聪明,也跟flutter的思想有所契合,关键就在于把数据于UI绑定了起来,由数据驱动UI改变,少了很多get() set()方法,实现了“所见即所得”
首先在Android的gradle中配置databindding开启,因为databinding是谷歌的官方库,所以加这一行自动集成了相关代码:
dataBinding {
enabled true
}
一.Binding类生成及使用
在项目的activity中初始化 注入绑定,这里有几种不同的方法,根据项目需要使用
- 1.修改BaseActivity类,适合新建的项目,用 DataBindingUtil.setContentView
这个ActivityDemoBinding类是由databinding根据xml文件自动生成的,跟butterknife一样的原理
activity_demo.xml :
- 2.直接在Activity实现类绑定xml,还是上图的那个activity,这次不改动原有的项目架构,在demoactivity中绑定xml,适合在原有的项目基础上修改
其实DataBindingUtil.setContentView 方法最后也是调用的bind()方法去绑定布局,只是在前一步先调用了activity的setcontentView
DataBindingUtil.setContentView :
二.构建ViewModel
然后在具体的activity中生成对应的viewmodel对象
@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.bind(getContainerView());
mBinding.setModel(new DemoViewModel());
}
在viewModel中声明几个数据:
public class DemoViewModel {
public ObservableField<String> demoString = new ObservableField<>("");
public ObservableBoolean demoBoolean = new ObservableBoolean();
public ObservableInt demoNum = new ObservableInt();
public void onButtonClick(View v){
//TODO
}
}
其中ObservableInt 这种是databinding中的类,封装了一些基本数据类型,也可以自定义封装自己的类,主要实现了监听回调接口,使用观察者模式,在数据变化的时候通知绑定的View刷新UI
android获取View一般是用butterKnife或者更原始一点要用到findviewById(),这类方法非常冗余,比如好用的butterKnife,必须在acticity先声明View对象,View类型,View的id,才能去设置文字或图片之类的东西,而使用databinding则可以直接获取xml布局的映射类,通过binding对象,在Activity中不用声明,直接拿来用,甚至一些简单的操作,如设置文字,图片,点击事件根本不需要activity介入,直接在xml中绑定了具体数据或方法。
使用方法,xml中:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="model"
type="com.XXX.DemoViewModel" />
<import type="android.view.View"/>
</data>
<FrameLayout
android:id="@+id/container_demo"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_demo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{model.demoString}"
android:visibility="@{model.demoBoolean? View.VISIBLE : View.GONE}"
/>
<Button
android:id="@+id/bt_demo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{model::onButtonClick}"
/>
</FrameLayout>
</layout>
在根布局中加入layout标签,加入data标签,type是上面baseActivity创建的viewModel
最基础的textview显示:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF4A4A4A"
android:textSize="18sp"
android:drawablePadding="10dp"
android:text="@{model.demoString,default= 123}"
/>
editext
<EditText
android:layout_width="match_parent"
android:layout_weight="1"
...
android:text="@={viewModel.phoneNo}"
binding:textChanged="@{viewModel.textChangeCommand}"
/>
两者的区别在于"@{}"和"@={}"的区别,前者单向绑定,数据变化会通知UI变化,而后者是双向绑定,UI变化也会通知数据的变化,典型的就是Edittext输入框
如果使用原始的方式,如findViewById或者Butteknife 需要在activity一开始声明一个view对象,需要用到View的id及类型,并且点击事件需要一个一个地去添加,否则点击不会起作用,获取界面数据的话需要在输入框使用edittext.getText().toString()才能获取到用户输入数据,非常繁琐,任何一步出错,就会导致bug的出现,影响后面所有操作,而使用databinding,不需要在activity中声明View对象,不需要关注View的类型,不需要关注View的id,只需要在xml中把数据绑定到View上就可以,并且由于bindingadapter的存在,还能做一些中间操作,如防止连续快速点击,加载网络图片等,把很多步骤都简化为一步实现
三.BindingAdapter
像平常的开发中,我们不止使用View的基本属性,在自定义View中,ImageView加载网络图片这些,还是需要获取到View对象在Java或kotlin代码中去实现一些操作,那有没有方式用databinding办到呢,这里就要说到BindingAdapter了,听名字就知道,是View的适配器,采用AOP的模式,在编译时生成代码,修改binding类中用了dadpter的View
比如一个Imageview设置头像,普通方法是获取imageview对象,然后去用glide或其他图片请求框架加载Url,用dababinding则是这样子:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
binding:url="@{model.imgUrl}"
binding:placeholderRes="@drawable/bg1"
/>
binding:url 指定图片地址
binding:placeholderRes 占位图片
这是怎么做到的呢,这里就要引入bindingAdapter
@BindingAdapter(value = {"url", "placeholderRes"}, requireAll = false)
public static void setImageUri(ImageView imageView, String url, int placeholderRes) {
if (!TextUtils.isEmpty(url)) {
//使用Glide框架加载图片
Glide.with(imageView.getContext())
.load(url)
.apply(new RequestOptions().placeholder(placeholderRes))
.into(imageView);
}
}
这里使用@bindingAdapter注解,value是url和placeholder,requireAll是否需要全部参数,然后这里的第一个参数ImageView就指定了view的类型,然后再使用Glide请求网络图片,placeholder设置占位图片,配置一个地方的代码,在所有的imageView都能够使用.
还有很多常用的方法,比如控件的圆角,根据不同type显示不同图片,按下改变背景,列表的item动画,列表的分隔符...等等都能用Adapter来完成,大大减少了Activity中的代码量,并且能够统一格式问题,改一处,处处生效
需要注意的是,bindingadapter在项目中任何一处写过就会生效,因为是用AOP模式,在编译时会检查代码,不用重复写,当bindingadapter和xml中原有属性冲突时,bindingdadpter中设置会覆盖原有属性
使用心得
优点:
代码量大大减少
bindingadapter统一