在真正接触并使用MVVM架构的时候,整我的都很差了。由于我的以为,MVVM相对于MVC、MVP学习难度比较大,设计的知识点不是一点半点。因此想慢慢记录下本身的成长。若有错误但愿指正。java
从零开始搭建MVVM架构系列文章(持续更新):
Android从零开始搭建MVVM架构(1)————DataBinding
Android从零开始搭建MVVM架构(2)————ViewModel
Android从零开始搭建MVVM架构(3)————LiveData
Android从零开始搭建MVVM架构(4)————Room(从入门到进阶)
Android从零开始搭建MVVM架构(5)————Lifecycles
Android从零开始搭建MVVM架构(6)————使用玩Android API带你搭建MVVM框架(初级篇)
Android从零开始搭建MVVM架构(7) ———— 使用玩Android API带你搭建MVVM框架(终极篇)android
首先看一张图,(这里就是一些人口中所说的“AAC框架”)git
我说下个人理解:AAC(Android Architecture Components) :其实是android官方提供的一系列组件,用来实现MVVM架构的。 这里提下 lifecycles:就是处理UI界面的生命周期,在26版本之后的Support库中,AppCompatActivity和SupportActivity中都实现了LifecycleOwner,内部已经对UI界面的生命周期作了处理了。咱们能够直接代码点进去,以下github
好了,回到DataBinding。这是MVVM框架的第一步。DataBinding是studio自带的。只须要在咱们app build.gradle的android标签下加上:markdown
dataBinding { enabled = true } 复制代码
DataBinding最厉害的功能是能够将咱们的数据和view绑定。这句话体现不出来,那能够说成,DataBinding能够将数据和xml绑定。并且还支持双向绑定:意思你改了bean里的数据,他会自动改变view里显示的数据。你改了xml里的数据,如editText里的数据,他会自动改变bean里的数据。
在android标签加上后,来到咱们的xml布局下,对着xml的第一行,按下Alt + Enter,选择 “Convert to data binding layout”,就能够生成DataBinding的布局规则网络
<?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); } 复制代码
此外还能够自定义生成类名,这里我就自定义了类名MyBindingapp
<data class="MyBinding"> </data> 复制代码
在xml里的<data>标签里加上要设置的数据,我这里有一个String,有一个OnClickListener:框架
<?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下)。以下就实现了一个设置数据,和设置点击事件。dom
经过如下代码能够看到,只要绑定下布局,经过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}" /> 复制代码
这里咱们先定义同名的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。也不会崩溃
在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。看这个名字就知道有点相似观察者模式
首先咱们定义个以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的颜色,说明
继承了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("看看刷新了哪", "未知错误~"); } } }); 复制代码
其实这个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); 复制代码
一看就是集合,这里和咱们经常使用的 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的布局以下:
<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是当前传入的值。
简单介绍下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); } 复制代码
这里比较重要的用法是,当咱们的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的值。太简单就不写了。