在真正接触并使用MVVM架构的时候,整个人都不好了。因为个人觉得,MVVM相对于MVC、MVP学习难度比较大,设计的知识点不是一点半点。所以想慢慢记录下自己的成长。如有错误希望指正。
首先看一张图,(这里就是一些人口中所说的“AAC框架”)
我说下我的理解:AAC(Android Architecture Components) :实际上是android官方提供的一系列组件,用来实现MVVM架构的。
这里提下 lifecycles:就是处理UI界面的生命周期,在26版本以后的Support库中,AppCompatActivity和SupportActivity中都实现了LifecycleOwner,内部已经对UI界面的生命周期做了处理了。我们可以直接代码点进去,如下
好了,回到DataBinding。这是MVVM框架的第一步。DataBinding是studio自带的。只需要在我们app build.gradle的android标签下加上:
dataBinding {
enabled = true
}
一、初始DataBinding(建议使用studio3.5,好用)
DataBinding最厉害的功能是可以将我们的数据和view绑定。这句话体现不出来,那可以说成,DataBinding可以将数据和xml绑定。而且还支持双向绑定:意思你改了bean里的数据,他会自动改变view里显示的数据。你改了xml里的数据,如editText里的数据,他会自动改变bean里的数据。
在android标签加上后,来到我们的xml布局下,对着xml的第一行,按下Alt + Enter,选择 “Convert to data binding layout”,就可以生成DataBinding的布局规则
生成如下,我这里改成了RelativeLayout布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>
</layout>
Activity里需要绑定下布局,框架自动会生成DataBinding类,类名是:xml名称+Binding。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
}
此外还可以自定义生成类名,这里我就自定义了类名MyBinding
<data class="MyBinding">
</data>
1.1、设置数据及点击事件
在xml里的<data>标签里加上要设置的数据,我这里有一个String,有一个OnClickListener:
- <variable>标签里的 name相当于数据引用
- type 是数据类型,也可以理解为包名.类名。常用数据类型,直接写类型
- 在设置值的时候的时候用@{value},value就是<variable>里的数据引用
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="textStr"
type="String" />
<variable
name="onClickListener"
type="android.view.View.OnClickListener" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{textStr}"
android:onClick="@{onClickListener}"
/>
</RelativeLayout>
</layout>
那么在Activity设置数据和设置点击事件(意思设置了variable标签后DataBinding会自动生成get和set方法。studio3.5后只要xml写上,IDE会自动生成,低版本可能需要Make Project下)。如下就实现了一个设置数据,和设置点击事件。
通过以下代码可以看到,只要绑定下布局,通过Binding对象,可以做任何事。从此不再使用findViewById,亦或是butterKnife。
public class BaseUseActivity extends AppCompatActivity implements View.OnClickListener {
private ActivityBaseuseBinding baseuseBinding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_baseuse);
baseuseBinding = DataBindingUtil.setContentView(this, R.layout.activity_baseuse);
baseuseBinding.setTextStr("这里就能设置数据");
baseuseBinding.setOnClickListener(this);
}
@Override
public void onClick(View v) {
baseuseBinding.txt.setText("点击设置的数据");
}
}
这里还能调用类里的方法,且需要特别注意,在使用DataBinding的时候,包名一定是小写,不然找不到包名,假设我们这里定义个类,然后调用类里的方法。
public class OnClickUtil {
public void onClickWithMe(View view) {
Toast.makeText(view.getContext(), "调用类里的方法", Toast.LENGTH_SHORT).show();
}
}
其他步骤都一样,唯一不同的是,调用类里方法的写法不同。假设button点击调用。用::表示调用,后面接的是方法名。
<Button
...
android:onClick="@{onClickUtil::onClickWithMe}"
/>
1.2、<import>和别名alias的使用
这里我们先定义同名的2个类User。放在不同包里。
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
之前我们的<data>标签就可以用<import>。
<data>
<import type="com.lihang.databindinglover.bean.User"/>
<variable
name="use_first"
type="User" />
</data>
<import>的用法是在同一个xml里需要用到多次User的时候,type类型只需要写<import>的类名就可以代表了,就不需要总是写包名.类型。但这个时候也就出现2个同名不同包的类是需要用到alias别名,不然类名重复了。
<data>
<import type="com.lihang.databindinglover.bean.User"/>
<import
alias="loverUser"
type="com.lihang.databindinglover.User"/>
<variable
name="user_first"
type="User" />
<variable
name="user_second"
type="loverUser" />
</data>
Activity里的使用都是非常简单的,如果有不明白,稍后放出链接。
这里还有特殊功能,比如我们再布局预览页面。通常会使用 tools:text="中间的"来预览布局,这个时候可以通过
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name,default = 预览文字}"
/>
使用default的时候,即使是studio3.5里,也不提示,不过不影响。还有一点,在DataBinding里,已经处理了null。所以这个时候你在Activity里给user设置为null。也不会崩溃
二、DataBinding在Fragment和RecyclerView里的使用
在Fragment的使用和Activity里的使用一样。获取根目录的方式如下。
//注意获取根布局是
View view = activityAlisBinding.getRoot();
这里重点介绍下再recyclerView里的用法。我们以前是不是写ViewHolder写的烦了?用上了DataBinding后,这么告诉你一个ViewHolder就能搞定一切需要的ViewHolder
先看下我们的唯一的ViewHolder。首先提下,自动生成的Binding的父类都是ViewDataBinding。我是把ViewHolder单独拉出来了。这样大家都能用:
public class NewViewHolder extends RecyclerView.ViewHolder {
public ViewDataBinding binding;
public NewViewHolder(ViewDataBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
在Adapter里只需要职业
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
ItemNewOrderBinding binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.item_new_order, viewGroup, false);
return new NewViewHolder(binding);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
NewViewHolder newViewHolder = (NewViewHolder) viewHolder;
//如果是多布局,那么对binding进行一个 instansof的一个判断就好。这样我们的ViewHolder永远只需要一个
ItemNewOrderBinding binding = (ItemNewOrderBinding) newViewHolder.binding;
binding.txtName.setText("这样就能使用了!!");
}
三、单向数据绑定
单向绑定可以理解为,改变了bean对象里的数据,就会自动改变我们xml的显示。这里涉及到3个类: BaseObservable、ObservableField、ObservableCollection。看这个名字就知道有点类似观察者模式
3.1、BaseObservable
首先我们定义个以Dog类
public class Dog extends BaseObservable {
//如果是public修饰的,直接用@Bindable
@Bindable
public String name;
//如果是private修饰的,则在get方法使用@Bindable
private String color;
public void setDataOnlyName(String name, String color) {
this.name = name;
this.color = color;
//只刷name字段
notifyPropertyChanged(com.lihang.databindinglover.BR.name);
}
public void setDataAll(String name, String color) {
this.name = name;
this.color = color;
//刷新全部字段
notifyChange();
}
...//省略部分代码
}
这里我同事改变了name和color的颜色,说明
- bean对象需要继承 BaseObservable
- @Bindable 标注用来表示哪个字段需要单向绑定。public修饰的可以直接用@Bindable绑定。private修饰的需要在get()方法上用@Bindable标注
- notifyChange();刷新所有字段,notifyPropertyChanged(com.lihang.databindinglover.BR.name);刷新单个字段。注意这里说的刷新全是被@Bindable绑定的。如果BR.name出不来。建议build下项目
- 还有不明白的可以在末尾链接demo看:单向数据绑定 – BaseObservable.
继承了BaseObservable的bean对象,还可以监听刷新了哪
dog.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
if (propertyId == com.lihang.databindinglover.BR.name) {
Log.e("看看刷新了哪", "刷新了name");
} else if (propertyId == com.lihang.databindinglover.BR._all) {
Log.e("看看刷新了哪", "全部全部");
} else {
Log.e("看看刷新了哪", "未知错误~");
}
}
});
3.2、ObservableField
其实这个ObservableField就是对BaseObservable的简化,不用继承,不用主动调刷新代码。
这个时候我们顶一个Human类
public class Human {
//这里必须是常量,ObservableField<参数类型>
//其实写上了下面一句,就是BaseObservable,set,get, @Bindable,刷新都封装了。直接看构造方法
public final ObservableField<String> name = new ObservableField<>();
//其中也封装了基本数据类型:ObservableInt等
public final ObservableInt age = new ObservableInt();
public Human(String name,int age){
this.name.set(name);
this.age.set(age);
}
}
Activity和xml里的操作和之前的一样,改变数据,自动改变xml只需要:
//简直太方便了吧
human.name.set("玉玑子");
human.age.set(15);
3.3、ObservableCollection
一看就是集合,这里和我们常用的 List Map一样。只不过这里的ObservableList、ObservableMap是封装好的。当我们改变集合里的数据时。xml也会改变。唯一要注意的是,在xml里引用这些集合的时候<类型>,这些符号,会影响xml格式所以要转义。用< 代表<;用>代表>(这些转义符,同样支持Mark Down);想了解更多可自行百度 DataBinding转义符。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="list"
type="androidx.databinding.ObservableList<String">>" />
<variable
name="map"
type="androidx.databinding.ObservableMap<String,String">>" />
<variable
name="index"
type="int" />
<variable
name="key"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list[index],default = 哈哈}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{map[key],default = 呵呵}" />
</LinearLayout>
</layout>
这里遇到一个坑,就是你的default = “默认值” 这个默认值最好不是data里的引用。不然会报错哦。这里我们带入index = 0 带入,把key = name。代入,然后动态改变,集合里这2个值:
@Override
public void onClick(View v) {
int randowInt = new Random().nextInt(100);
switch (v.getId()){
case R.id.btn_index:
//改变list的第一项
list.add(0,"list的值" + randowInt);
break;
case R.id.btn_key:
map.put("name","map的值" + randowInt);
break;
}
}
四、双向数据绑定
意思就是你改变bean对象里的值,他会主动改变xml的显示,改变xml的里的值,他会把bean对象里的属性改变了。
这里我们用1个TextView显示数据;用1个EditTextView绑定bean对象,再用1个Button可以动态查询bean对象里的属性值
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="human"
type="com.lihang.databindinglover.bean.Human" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{human.name}" />
<EditText
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="20dp"
android:text="@={human.name}" />
<Button
android:id="@+id/btn_search"
android:layout_marginTop="60dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="动态查询属性"
/>
</LinearLayout>
</layout>
bean对象绑定xml显示:单向绑定是@{属性值},双向绑定则是@={属性值},效果如下:
五、在include 和 viewStub中使用
5.1 在include中使用。
include的布局如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.lihang.databindinglover.bean.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}" />
</LinearLayout>
</layout>
Activity里引用include这样:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="user"
type="com.lihang.databindinglover.bean.User" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="@layout/include_item"
app:user="@{user}" />
</RelativeLayout>
</layout>
注意:app:user="@{user}"。第一个user是include里name的引用。第二user是当前传入的值。
5.2、viewStub中的使用
简单介绍下viewStub:被viewStub包裹的。即使页面显示的时候,被包裹的布局也不会加载,除非调用inflate。这样算是对布局卡顿的优化了。include则算是代码里的布局优化。
直接放Activity布局了。被包裹的布局和上面的include一样
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="user"
type="com.lihang.databindinglover.bean.User" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewStub
android:id="@+id/view_stub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout="@layout/viewstub_layout"
app:user="@{user}" />
</RelativeLayout>
</layout>
activity里:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewstub);
binding = DataBindingUtil.setContentView(this, R.layout.activity_viewstub);
User user = new User("我爱学习", 18);
binding.viewStub.getViewStub().inflate();
binding.setUser(user);
}
六、@BindingAdapter的使用。
这里比较重要的用法是,当我们的imageView需要加载网络url时,假如用的是glide去加载,这个时候就需要使用@BindingAdapter。
这个需要一个辅助类:new一个辅助类后,xml里就可以使用了。有点像Dagger2
public class DataBindingHelper {
//用@BindingAdapter标注,有点类似自定义属性,后面是属性名,方法体类似得到属性值后去做的事情。
//第一个参数:是当前的控件类型,其实也可以写成View,但是要加载还是要判断是否是imageView
//第二个参数:是网络加载的url。
@BindingAdapter("imageWithGlide")
public static void loadImage(ImageView imageView, String url) {
Glide.with(imageView).load(url)
.placeholder(R.mipmap.ic_launcher)
.error(R.mipmap.ic_launcher)
.transition(withCrossFade())
.centerCrop()
.into(imageView);
}
//@BindingAdapter还能修改系统属性值,这是修改textView的属性,意思只要使用DataBinding给textView设置setText值的,
//都会加上后面这段 " - 我是通过方法加的"
//我这里先注释掉了。不然整个项目的textView都会加上整个,如果要测试,可以打开
//@BindingAdapter("android:text")
//public static void setText(TextView textView, String testStr) {
// textView.setText(testStr + " - 我是通过方法加的");
//}
}
我们的xml就是专业:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="imageUrl"
type="String" />
<variable
name="testStr"
type="String" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/img"
android:layout_centerHorizontal="true"
android:layout_marginTop="15dp"
android:text="@{testStr}" />
<ImageView
android:id="@+id/img"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true"
app:imageWithGlide="@{imageUrl}" />
</RelativeLayout>
</layout>
activity里就是设置imageUrl和testStr的值。太简单就不写了。
七、DataBinding布局里支持的语法
支持的语法:
- 算术 + - / * %
- 字符串合并 +
- 逻辑 && ||
- 二元 & | ^
- 一元 + - ! ~
- 移位 >> >>> <<
- 比较 == > < >= <=
- Instanceof
- Grouping ()
- character, String, numeric, null
- Cast
- 方法调用
- Field 访问
- Array 访问 []
- 三元 ?:
不支持的语法:
- this
- super
- new
- 显示泛型调用