MVVM:MVVM是Model-View-ViewModel的简写,双向数据绑定,数据驱动UI;通过databinding的组件,负责将view和Model进行绑定;
MVVM:内存消耗比较大,每一个对象在内存中产生一个副本,而且没刷新一次UI都会开一个线程,也比较耗电,但是开发的速度是各种模式里面最快的一种;
先设置dataBinding为可用
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
dataBinding{ //配置dataBinding可用
enabled true
}
......
}
来个简单的布局-Activity中使用
1、整体布局的最外层标签用layout包裹起来;
2、设置我们的数据源,data标签中使用variable标签,name是别名,这个随便取;type是全类名;
3、布局中使用:使用这样的表达式@{user.userName}去取值;
注意
1、注意到app:header="@{user.header}“这个属性了吗?这里是用在ImageView标签上面的,也就是用来显示图片;
2、 app:自定义属性名=”@{url}";具体这个自定义属性名在哪定义,和它里面的值怎么做到显示图片的,我们接着往下看;
<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.lk.mymvvm.entity.User" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
app:header="@{user.header}"
android:onClick="goToListActivity">ImageView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`姓名:`+user.userName}"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`密码:`+user.password}"
/>
LinearLayout>
layout>
再来看看这个Activity中的代码
这里不适用原来的setContentView来做布局设置了;
这里需要用到DataBindingUtil.setContentView(this,layoutId);返回了一个ActivityMainBinding对象;
给布局设置数据源: binding.setUser(user);
我这里做了个延时数据更改,想要做到数据更改,页面布局中也会跟着变化;
package com.lk.mymvvm;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import com.lk.mymvvm.databinding.ActivityMainBinding;
import com.lk.mymvvm.entity.User;
public class MainActivity extends AppCompatActivity {
User user;
Handler handler= new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
//绑定布局
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
user = new User("lk","123456","http://b-ssl.duitang.com/uploads/item/201704/10/20170410095843_SEvMy.thumb.700_0.jpeg");
//设置数据
binding.setUser(user);
//我们来做个延时修改数据,用于模拟数据双向绑定的修改
handler.postDelayed(new Runnable() {
@Override
public void run() {
user.setUserName("kai");
user.setPassword("654321");
user.setHeader("http://img5.imgtn.bdimg.com/it/u=3465022816,2610872513&fm=11&gp=0.jpg");
}
},3000);
}
public void goToListActivity(View view) {
Intent intent = new Intent(this,ListActivity.class);
startActivity(intent);
}
}
最后我们来看看这个User实体类
BaseObservable:继承这个类,看名字被观察者;
@Bindable 这个注解标注在我们需要改变的属性的get方法上面,这样自动生成BR类中,才会有这个属性id;
接下来需要在值更改的时候,通知界面刷新; notifyPropertyChanged(BR.userName); 这个方法中的id就是BR给我们生成的属性id;这样在外面调用set方法,这个值赋值之后,会去通知界面刷新;从而做到双向数据绑定的效果;
注意看这个注解 @BindingAdapter(“bind:header”),是否还记得我们在布局中的ImageView标签中的app:header="@{user.header}"这个属性?app:自定义属性=“数据”,这个类中的bind:header,就是自定义的属性名;
最后我们自定义了getImage这个方法,在方法中做了图片加载;
一定要记住继承、注解、通知界面刷新,界面才会做到数据更改进而更改布局中的值
package com.lk.mymvvm.entity;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import com.squareup.picasso.Picasso;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import androidx.databinding.BindingAdapter;
import androidx.databinding.library.baseAdapters.BR;
/**
* 这个类就相当于一个ViewModel
* 继承BaseObservable 用于通知界面更新
*/
public class User extends BaseObservable {
private String userName;
private String password;
private String header;
public User(String userName, String password,String header) {
this.userName = userName;
this.password = password;
this.header = header;
}
//注解@Bindable在编译期间生成一个BR类
@Bindable
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
//通知界面更新
notifyPropertyChanged(BR.userName);
}
//注解@Bindable在编译期间生成一个BR类
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
//通知界面更新
notifyPropertyChanged(BR.password);
}
/**
* 自定义属性:提供一个静态方法来加载image 必须使用静态
* bind:header 这个header是和数据xml中的自定义属性app:header="" 这个需要一致
* @param imageView
* @param url url就是pp:header="@{user.header}"中的图片url
*/
@BindingAdapter("bind:header")
public static void getImage(ImageView imageView,String url){
//这里我们用了个Picasso框架来加载图片
Picasso.with(imageView.getContext()).load(url).into(imageView);
}
@Bindable
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
//通知界面更新
notifyPropertyChanged(BR.header);
}
}
我们接下来在列表中去使用
ListView中的每一个item去做数据双向绑定
Activity中引用的布局文件;
因为这里不需要做数据绑定,而是要去给listView的每一项去做绑定,所以这里没有用到上面那布局的layout标签;
就一个简单的ListView;
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent">ListView>
LinearLayout>
1、我们来看看这个item的布局
基本写法和上面那个布局差不多;
但是我们这里是用在ListView的每一个item中去的;
需要注意的是,给最后那个TextView标签加了点击事件;
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="user1"
type="com.lk.mymvvm.entity.User"/>
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
app:header="@{user1.header}"
android:layout_width="50dp"
android:layout_height="50dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@{user1.userName}"/>
<TextView
android:onClick="@{user1.click}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@{user1.password}"/>
LinearLayout>
layout>
2、ListView最核心的地方是适配器啦,我们来看看这个适配器
在MVVM中,列表的适配器,基本可以做一个通用的适配器;
这里我们写了个泛型的适配器;
接收外部传入进来的上下文、布局解析器、布局id、节点id、以及数据源
除了getView方法重写起来不一样,其他三个方法还是原来的写法;
getView方法中:
先判断布局是否存在,我想大家都不陌生,这里是放置内存多余的消耗,做布局复用;
如果视图不存在,我们就创建了ViewDataBinding对象,有的话直接拿出来重用;
绑定variableId,并将值设置进视图中;
并通过dataBinding.getRoot().getRootView()获取到视图,return出去了;
这个通用的适配器,就可以直接拿着实用了,不用多次新建适配器了
package com.lk.mymvvm.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import java.util.List;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;
/**
* 使用MVVM的时候可以写个这样的通用适配器
* @param
*/
public class CommAdapter<T> extends BaseAdapter {
private Context context;
//布局解析器
private LayoutInflater inflater;
//布局id
private int layoutId;
//这个id会自动生成 等于是跟节点(对应布局中的data标签中的variable节点)对应的id
private int variableId;
//数据源
private List<T> list;
public CommAdapter(Context context, LayoutInflater inflater, int layoutId, int variableId, List<T> list) {
this.context = context;
this.inflater = inflater;
this.layoutId = layoutId;
this.variableId = variableId;
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewDataBinding dataBinding;
if(convertView==null){
dataBinding = DataBindingUtil.inflate(inflater,layoutId,parent,false);
}else{
//重用之前的
dataBinding = DataBindingUtil.getBinding(convertView);
}
//绑定variableId
dataBinding.setVariable(variableId,list.get(position));
//视图返回出去
return dataBinding.getRoot().getRootView();
}
}
3、接下里我们来看看调用
这个实用的地方,值得注意的地方,就是第四个参数;
也就是适配器中让我们传入的variableId;注意看这里用到的是BR.user1,有没有注意到这个user1,在上面那个布局代码中使用了?别名用在哪了?
嘿嘿,去找找吧~~
package com.lk.mymvvm;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import com.lk.mymvvm.adapter.CommAdapter;
import com.lk.mymvvm.entity.User;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.Nullable;
public class ListActivity extends Activity {
ListView listView;
List<User> data = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list);
initData();
listView = findViewById(R.id.listView);
//设置适配器,设置的是写的通用的适配器
listView.setAdapter(new CommAdapter<User>(this,getLayoutInflater(),R.layout.item,BR.user1,data));
}
private void initData() {
data.add(new User("lk1","123456","http://img5.imgtn.bdimg.com/it/u=3465022816,2610872513&fm=11&gp=0.jpg"));
data.add(new User("lk1","123456","http://b-ssl.duitang.com/uploads/item/201901/09/20190109072726_aNNZd.thumb.700_0.jpeg"));
data.add(new User("lk1","123456","http://img5.imgtn.bdimg.com/it/u=3465022816,2610872513&fm=11&gp=0.jpg"));
data.add(new User("lk1","123456","http://b-ssl.duitang.com/uploads/item/201901/09/20190109072726_aNNZd.thumb.700_0.jpeg"));
}
}
以上就是我们在简单布局中使用数据双向绑定,以及在ListView中去使用;
如果在写代码的过程中,发现找不到BR类,或者找不到这些id,记得ReBuild一下,既然了解到需要ReBuild下才能找到这个类,那么大致都应该知道这里肯定是使用了注解处理器编译时生成类咯~~~
我们来看看这两个路径下的文件
build下面,那么我们就知道这里是编译时给我们生成的文件了吧?
app\build\intermediates\data_binding_layout_info_type_merge\debug\mergeDebugResources\out\activity_main-layout.xml
<Layout layout="activity_main" modulePackage="com.lk.mymvvm" filePath="E:\AndroidTools\AndroidObj\MyMVVM\app\src\main\res\layout\activity_main.xml" directory="layout" isMerge="false">
<Variables declared="true" type="com.lk.mymvvm.entity.User" name="user">
<location startLine="8" startOffset="8" endLine="10" endOffset="46"/>
Variables>
<Targets>
<Target tag="layout/activity_main_0" view="LinearLayout">
<Expressions/>
<location startLine="13" startOffset="4" endLine="32" endOffset="18"/>
Target>
<Target tag="binding_1" view="ImageView">
<Expressions>
<Expression text="user.header" attribute="app:header">
<Location startLine="20" startOffset="12" endLine="20" endOffset="38"/>
<TwoWay>falseTwoWay>
<ValueLocation startLine="20" startOffset="26" endLine="20" endOffset="36"/>
Expression>
Expressions>
<location startLine="17" startOffset="8" endLine="21" endOffset="58"/>
Target>
<Target tag="binding_2" view="TextView">
<Expressions>
<Expression text="`姓名:`+user.userName" attribute="android:text">
<Location startLine="25" startOffset="12" endLine="25" endOffset="48"/>
<TwoWay>falseTwoWay>
<ValueLocation startLine="25" startOffset="28" endLine="25" endOffset="46"/>
Expression>
Expressions>
<location startLine="22" startOffset="8" endLine="26" endOffset="12"/>
Target>
<Target tag="binding_3" view="TextView">
<Expressions>
<Expression text="`密码:`+user.password" attribute="android:text">
<Location startLine="30" startOffset="12" endLine="30" endOffset="48"/>
<TwoWay>falseTwoWay>
<ValueLocation startLine="30" startOffset="28" endLine="30" endOffset="46"/>
Expression>
Expressions>
<location startLine="27" startOffset="8" endLine="31" endOffset="13"/>
Target>
Targets>
Layout>
app\build\intermediates\incremental\mergeDebugResources\stripped.dir\layout\activity_main.xml
这个文件中,他把我们的data标签整个都去掉了吧?
并在每个标签上添加了tag属性,等于是做了标记
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" android:tag="layout/activity_main_0" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:tag="binding_1"
android:onClick="goToListActivity">ImageView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="binding_2"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="binding_3"
/>
LinearLayout>
我们两个文件结合起来看
注意
我们结合上面的这个生成的xml文件来看;
登记了实体类信息,并标注了代码在我们自己写的布局中的起始位置、结束位置;
<Variables declared="true" type="com.lk.mymvvm.entity.User" name="user">
<location startLine="8" startOffset="8" endLine="10" endOffset="46"/>
Variables>
第一个Target,里面的tab标签对应的是生成的下面按个xml中的LinearLayout标签里面的tag吧?
然后标记了标签的起始位置、结束位置
<Targets>
<Target tag="layout/activity_main_0" view="LinearLayout">
<Expressions/>
<location startLine="13" startOffset="4" endLine="32" endOffset="18"/>
Target>
......
Targets>
第二个Target,里面的tag也是对应了下面按个xml中的ImageView标签的tag吧?
标记了起始位置、结束位置
<Target tag="binding_1" view="ImageView">
<Expressions>
<Expression text="user.header" attribute="app:header">
<Location startLine="20" startOffset="12" endLine="20" endOffset="38"/>
<TwoWay>falseTwoWay>
<ValueLocation startLine="20" startOffset="26" endLine="20" endOffset="36"/>
Expression>
Expressions>
<location startLine="17" startOffset="8" endLine="21" endOffset="58"/>
Target>
下面那几个标签,这里就不贴代码了,这两个文件相互依赖;
既然生成两个这样的文件,肯定就会去做做些这两个文件吧?
既然是编译时生成的类,那么我们就在这个下面找到它们
我们来看看
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);这个方法调用;点进去,我们来上伪代码
activity.setContentView(layoutId); 这类调用的是Activity的setContentView来做绑定了布局吧?
接下来我们看到bindToAddedViews这个方法;
判断下是否是一个或者多个控件吧?
不管是一个还是多个最终调用了bind方法;
在bind方法中调用了这个DataBinderMapperImpl实现类的getDataBinder方法;
public class DataBindingUtil {
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
......
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
......
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId, @Nullable DataBindingComponent bindingComponent) {
activity.setContentView(layoutId);
View decorView = activity.getWindow().getDecorView();
ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}
......
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}
......
private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
ViewGroup parent, int startChildren, int layoutId) {
//得到文件中控件的个数
final int endChildren = parent.getChildCount();
final int childrenAdded = endChildren - startChildren;
if (childrenAdded == 1) {
final View childView = parent.getChildAt(endChildren - 1);
return bind(component, childView, layoutId);
} else {
final View[] children = new View[childrenAdded];
for (int i = 0; i < childrenAdded; i++) {
children[i] = parent.getChildAt(i + startChildren);
}
return bind(component, children, layoutId);
}
}
}
来看看这个DataBinderMapperImpl实现类里面的实现
获取标签tag;做了个非空判断
然后根据布局去读取标签;如果找到这个布局中的顶层标签,标识这是我们的布局文件,然后去创建了一个ActivityMainBindingImpl对象并返回出去,反之抛异常;
package com.lk.mymvvm;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.View;
import androidx.databinding.DataBinderMapper;
import androidx.databinding.DataBindingComponent;
import androidx.databinding.ViewDataBinding;
import com.lk.mymvvm.databinding.ActivityMainBindingImpl;
import com.lk.mymvvm.databinding.ItemBindingImpl;
import java.lang.IllegalArgumentException;
import java.lang.Integer;
import java.lang.Object;
import java.lang.Override;
import java.lang.RuntimeException;
import java.lang.String;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class DataBinderMapperImpl extends DataBinderMapper {
private static final int LAYOUT_ACTIVITYMAIN = 1;
private static final int LAYOUT_ITEM = 2;
private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(2);
static {
INTERNAL_LAYOUT_ID_LOOKUP.put(com.lk.mymvvm.R.layout.activity_main, LAYOUT_ACTIVITYMAIN);
INTERNAL_LAYOUT_ID_LOOKUP.put(com.lk.mymvvm.R.layout.item, LAYOUT_ITEM);
}
@Override
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
if(localizedLayoutId > 0) {
//读取标签
final Object tag = view.getTag();
if(tag == null) {
throw new RuntimeException("view must have a tag");
}
switch(localizedLayoutId) {
//我们的activity_main布局
case LAYOUT_ACTIVITYMAIN: {
//匹配到了顶层layout/activity_main_0这个tag
if ("layout/activity_main_0".equals(tag)) {
return new ActivityMainBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
}
//我们的item布局
case LAYOUT_ITEM: {
//匹配到了顶层layout/item_0这个tag
if ("layout/item_0".equals(tag)) {
return new ItemBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for item is invalid. Received: " + tag);
}
}
}
return null;
}
@Override
public ViewDataBinding getDataBinder(DataBindingComponent component, View[] views, int layoutId) {
if(views == null || views.length == 0) {
return null;
}
int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
if(localizedLayoutId > 0) {
final Object tag = views[0].getTag();
if(tag == null) {
throw new RuntimeException("view must have a tag");
}
switch(localizedLayoutId) {
}
}
return null;
}
......
}
接下来看看创建的ActivityMainBindingImpl这个类
在构造方法找我那个,将对应的控件赋值,注意这里的控件是我们布局中使用的控件,在这里所有的控件都产生了副本,这就是MVVM为什么非常消耗内存的原因,Android加载了一套,这里还给生产一套,在内存中多了一套;
package com.lk.mymvvm.databinding;
import com.lk.mymvvm.R;
import com.lk.mymvvm.BR;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
@SuppressWarnings("unchecked")
public class ActivityMainBindingImpl extends ActivityMainBinding {
@Nullable
private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
@Nullable
private static final android.util.SparseIntArray sViewsWithIds;
static {
sIncludes = null;
sViewsWithIds = null;
}
// views
@NonNull
private final android.widget.LinearLayout mboundView0;
@NonNull
private final android.widget.ImageView mboundView1;
@NonNull
private final android.widget.TextView mboundView2;
@NonNull
private final android.widget.TextView mboundView3;
// variables
// values
// listeners
// Inverse Binding Event Handlers
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 4, sIncludes, sViewsWithIds));
}
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 1
);
//给控件赋值
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView1 = (android.widget.ImageView) bindings[1];
this.mboundView1.setTag(null);
this.mboundView2 = (android.widget.TextView) bindings[2];
this.mboundView2.setTag(null);
this.mboundView3 = (android.widget.TextView) bindings[3];
this.mboundView3.setTag(null);
setRootTag(root);
// 调用更新的方法
invalidateAll();
}
@Override
public void invalidateAll() {
synchronized(this) {
mDirtyFlags = 0x10L;
}
requestRebind();
}
@Override
public boolean hasPendingBindings() {
synchronized(this) {
if (mDirtyFlags != 0) {
return true;
}
}
return false;
}
//设置布局中data->Variable标签中的我们的对象
@Override
public boolean setVariable(int variableId, @Nullable Object variable) {
boolean variableSet = true;
if (BR.user == variableId) {
setUser((com.lk.mymvvm.entity.User) variable);
}
else {
variableSet = false;
}
return variableSet;
}
public void setUser(@Nullable com.lk.mymvvm.entity.User User) {
updateRegistration(0, User);
this.mUser = User;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.user);
super.requestRebind();
}
}
我们回到刚刚的DataBinderMapperImpl实现类中的getDataBinder这个方法的返回这里,这个返回了一个ViewDataBinding对象,我们大致看看ViewDataBinding这个类里面
这个静态块是会被执行的吧?
里面比对下了使用的SDK的版本,版本低于19的话,这里就不支持自动更新的功能;
19版本以上,会帮我们创建一个监听器,这个监听器在什么情况下会触发了?
在我们实体类中的set方法调用notifyPropertyChanged(BR.id);的时候触发;
这个监听器一旦触发以后,这里执行了一个线程;
在县城里面执行了绑定的功能,这个绑定的方法中,做了一下判断回调,我们去看看executeBindings这个方法,这里是个抽象方法,我们去实现类;
是否还记得在DataBinderMapperImpl类的getDataBinder方法是返回ViewDataBinding 这个对象吧?最终是new了一个ActivityMainBindingImpl对象吧?也就是executeBindings这个方法的实现是在ActivityMainBindingImpl这个类里面吧?我们返回去看看这个类
public abstract class ViewDataBinding extends BaseObservable {
......
private static final OnAttachStateChangeListener ROOT_REATTACHED_LISTENER;
static {
//SDK版本低于19,就不会给我们创建监听器
if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
ROOT_REATTACHED_LISTENER = null;
} else {
ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
@TargetApi(VERSION_CODES.KITKAT)
@Override
public void onViewAttachedToWindow(View v) {
// execute the pending bindings.
final ViewDataBinding binding = getBinding(v);
//监听器触发以后,这里执行了一个线程
binding.mRebindRunnable.run();
v.removeOnAttachStateChangeListener(this);
}
@Override
public void onViewDetachedFromWindow(View v) {
}
};
}
}
//监听器触发的时候,开启的线程
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
mPendingRebind = false;
}
processReferenceQueue();
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
// Nested so that we don't get a lint warning in IntelliJ
if (!mRoot.isAttachedToWindow()) {
// Don't execute the pending bindings until the View
// is attached again.
mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
return;
}
}
//执行了绑定功能
executePendingBindings();
}
};
/**
* Evaluates the pending bindings, updating any Views that have expressions bound to
* modified variables. This must be run on the UI thread.
*/
public void executePendingBindings() {
if (mContainingBinding == null) {
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}
private void executeBindingsInternal() {
if (mIsExecutingPendingBindings) {
requestRebind();
return;
}
if (!hasPendingBindings()) {
return;
}
mIsExecutingPendingBindings = true;
mRebindHalted = false;
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBIND, null);
// The onRebindListeners will change mPendingHalted
if (mRebindHalted) {
mRebindCallbacks.notifyCallbacks(this, HALTED, null);
}
}
if (!mRebindHalted) {
//上面做了一些判断,回调,我们去看看这个executeBindings方法
executeBindings();
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
}
}
mIsExecutingPendingBindings = false;
}
/**
* @hide
*/
protected abstract void executeBindings();
......
}
来看看ActivityMainBindingImpl 这个类中的executeBindings方法实现
这个executeBindings方法中基本就是在做创建成员变量,然后进行实体类的get方法取值吧?
取完值然后调用适配器里面的方法,给获取到并传入的控件进行文字的修改了吧? 注意我们上面在实体类中的设置图片的方法,记得是用的静态的方法static吧?这类通过类名.方法(),方法一调用是不是我们自己写的加载图片的方法,就加载了?给我们方法中传入了imageView控件,和url图片显示的路径
public class ActivityMainBindingImpl extends ActivityMainBinding {
......
@Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
switch (localFieldId) {
case 0 :
return onChangeUser((com.lk.mymvvm.entity.User) object, fieldId);
}
return false;
}
private boolean onChangeUser(com.lk.mymvvm.entity.User User, int fieldId) {
if (fieldId == BR._all) {
synchronized(this) {
mDirtyFlags |= 0x1L;
}
return true;
}
else if (fieldId == BR.header) {
synchronized(this) {
mDirtyFlags |= 0x2L;
}
return true;
}
else if (fieldId == BR.userName) {
synchronized(this) {
mDirtyFlags |= 0x4L;
}
return true;
}
else if (fieldId == BR.password) {
synchronized(this) {
mDirtyFlags |= 0x8L;
}
return true;
}
return false;
}
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String javaLangStringUserPassword = null;
com.lk.mymvvm.entity.User user = mUser;
java.lang.String userHeader = null;
java.lang.String javaLangStringUserUserName = null;
java.lang.String userUserName = null;
java.lang.String userPassword = null;
if ((dirtyFlags & 0x1fL) != 0) {
if ((dirtyFlags & 0x13L) != 0) {
if (user != null) {
// read user.header
userHeader = user.getHeader();
}
}
if ((dirtyFlags & 0x15L) != 0) {
if (user != null) {
// read user.userName
userUserName = user.getUserName();
}
// read ("姓名:") + (user.userName)
javaLangStringUserUserName = ("姓名:") + (userUserName);
}
if ((dirtyFlags & 0x19L) != 0) {
if (user != null) {
// read user.password
userPassword = user.getPassword();
}
// read ("密码:") + (user.password)
javaLangStringUserPassword = ("密码:") + (userPassword);
}
}
// batch finished
if ((dirtyFlags & 0x13L) != 0) {
// api target 1
//类名点方法(静态方法)这个方法一调用,图片也就加载,我们自己那边就做了图片加载吧?
com.lk.mymvvm.entity.User.getImage(this.mboundView1, userHeader);
}
if ((dirtyFlags & 0x15L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, javaLangStringUserUserName);
}
if ((dirtyFlags & 0x19L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView3, javaLangStringUserPassword);
}
}
// Listener Stub Implementations
// callback impls
// dirty flag
private long mDirtyFlags = 0xffffffffffffffffL;
/* flag mapping
flag 0 (0x1L): user
flag 1 (0x2L): user.header
flag 2 (0x3L): user.userName
flag 3 (0x4L): user.password
flag 4 (0x5L): null
flag mapping end*/
//end
}
在来看看这个TextViewBindingAdapter适配器的setText方法
看到这里
里面做了一下判断,判断了是否新的值和控件上的值是否是一样的,如果一样就不进行赋值;
最终调用的是TextView的setText方法进行赋值
public class TextViewBindingAdapter {
@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
final CharSequence oldText = view.getText();
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
if (text instanceof Spanned) {
if (text.equals(oldText)) {
return; // No change in the spans, so don't set anything.
}
} else if (!haveContentsChanged(text, oldText)) {
return; // No content changes, so don't set anything.
}
view.setText(text);
}
}
1、我们创建的布局,通过标签,里面包揽了data标签和布局,会给我们生产成本两套xml文件,一套是去掉了layout标签和data标签并且控件上带有tag标签的xml文件,一套是记录我们布局文件中控件、控件tag、以及控件位置的xml文件**
2、Activity(DataBindingUtil.setContentView)或者适配器(DataBindingUtil.inflate)中,去调用Android自己的设置布局的方法,然后调用在DataBinderMapperImpl类中的getDataBinder方法中读取控件中的tag标签,创建了ActivityMainBindingImpl对象;
3、创建生成的ActivityMainBindingImpl类的构造方法中去生产出控件的副本,赋值到全局控件变量中;
4、实体类中给get方法打上@Bindable注解的时候,会根据这个注解,生成BR类中的id;
5、在我们调用实体类中的set方法的时候,调用了被观察者的notifyPropertyChanged方法,这个时候ViewDataBinding这个类中静态初始化的监听器就收到回调,开启了一个Runnable调用了修改绑定值的方法,走入到实现类ActivityMainBindingImpl的这个executeBindings实现方法中,里面去进行了实体类的get方法取值赋值到局部变量中,最后调用了TextViewBindingAdapter适配器,把获取到的控件副本、获取到的值传入到这个适配器中
6、适配器最终调用了TextView的setText方法
注意:给图片进行加载更改的时候,是通过类中的静态方法,传给了我们自己,所以我们在这个静态方法中,自己使用了图片加载框架,进行加载的