DataBinding
2017-11-02 本文已影响0人
_xlh
dataBinding的使用
一、databinding的配置方法
module的gradle文件中添加
dataBinding {
enabled true
}
二、databinding的基本使用
跟布局标签使用layout标签
<layout>
<data>
<variable
name="user" //type的引用...
type="com.xlh.databinding.entity.UserEntity"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="@{user.name}"
android:textColor="@{user.vip?0xff0000:0x00ff00}"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:text="@{user.nickName}"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:text="@{user.email}"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
在编译之前使用布局文件生成1个类...
注意:如果在xml中没有定义<data>标签中的数据,则不会生成对应的实体类...
public class DataBindingActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_data_binding);
ActivityDataBindingBinding binding =
DataBindingUtil.setContentView(this,R.layout.activity_data_binding);
UserEntity userEntity = new UserEntity();
userEntity.setName("用户名");
userEntity.setNickName("昵称");
userEntity.setEmail("example@qq.com");
binding.setUser(userEntity); //一次性显示3个控件的内容
}
}
databinding中,单引号中可以出现双引号,双引号中只能出现反斜杠的引号
<TextView
android:text="@{user.name + `hello` + user.nickName}" //注意符号数字1左边的键
android:textColor="@{user.vip?0xff0000:0x00ff00}"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
三元运算符
<TextView
android:text="@{user.name == null? user.nickName:user.name}"
android:textColor="@{user.vip?0xff0000:0x00ff00}"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
databinding提供双问号来替代三元运算符
<TextView
android:text="@{user.name ?? user.nickName}"
android:textColor="@{user.vip?0xff0000:0x00ff00}"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
如果双问号前面的值不为null就显示前面的值,否则显示后面的值
双引号中不能出现双引号...字符串使用'~'这个键来拼接字符串...
xml中< >等的使用
<TextView
android:text="@{user.level}"
android:textColor="@{user.level < 3 ? 0xff00ff:0xffffff}"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
> 大于... < 小于...
其他转义字符自行百度...
三、databinding的监听设置
public class UserEntity {
public void clickName(View view) {
Toast.makeText(view.getContext().getApplicationContext(),
"点击了用户名!", Toast.LENGTH_SHORT).show();
}
public boolean longClickNickName(View view) {
Toast.makeText(view.getContext().getApplicationContext(), "长按了NickName!", Toast.LENGTH_SHORT).show();
return true;
}
<TextView
android:onClick="@{user.clickname}"
android:clickable="true"
android:text="@{user.name + `abc` }"
android:textColor="@{user.vip?0xff0000:0x0000ff}"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:onLongClick="@{user.longClickNickName}"
android:clickable="true"
android:text="@{user.nickName}"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
//自定义的方法名称在xml和Java中必须一致
//返回值类型和传入参数必须和需要映射的原生方法参数返回值类型一致
四、databinding添加自定义属性
//自定义属性:定义工具类...
使用@BindingAdapter注解申明自定义控件的属性...
public class ImageUtil {
/**
* 方法必须申明为静态
* @param view 给哪个控件添加自定义属性
* @param url 图片的url地址
* 注解的参数:
* imageUrl表示添加的自定义属性
*/
@BindingAdapter({"imageUrl"}) //如果没有加命名空间,databinding会自动去找imageUrl的属性
public static void downloadImage(ImageView view,String url) {
if(url == null) {
view.setImageResource(R.mipmap.ic_launcher);
}
else {
//TODO 执行下载显示图片的任务
}
}
<ImageView
app:imageUrl="@{user.icon}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
五、Component的使用
//使用bindingAdapter注解自定义属性,提供的方法不是static
//那么就需要自定义Component来使之有调用的对象
public class MyComponent implement DataBindingComponent{
@Override
public Utils getUtils() {
return ...
}
}
//设置component的方法
在setContentView之前调用DataBindingUtil.setComponent(自定义的component)
六、源码查看
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityDatabindingBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_databinding);
//...
}
private static DataBindingComponent sDefaultComponent = null;
public static void setDefaultComponent(DataBindingComponent bindingComponent) {
sDefaultComponent = bindingComponent;
}
public static DataBindingComponent getDefaultComponent() {
return sDefaultComponent;
}
public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId) {
//调用setContentView
return setContentView(activity, layoutId, sDefaultComponent);
}
public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId,
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);
}
private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
ViewGroup parent, int startChildren, int layoutId) {
//startChildren = 0
final int endChildren = parent.getChildCount();
final int childrenAdded = endChildren - startChildren;
//判断contentView是否只有1个控件
if (childrenAdded == 1) {
final View childView = parent.getChildAt(endChildren - 1);
return bind(component, childView, layoutId);
} else {
//创建子View数组
final View[] children = new View[childrenAdded];
for (int i = 0; i < childrenAdded; i++) {
children[i] = parent.getChildAt(i + startChildren);
}
return bind(component, children, layoutId);
}
}
//该mapper在加载类的时候申明
private static DataBinderMapper sMapper = new DataBinderMapper();
//调用重载的bind方法
@SuppressWarnings("unchecked")
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}
//重载的方法, 传入view而不是view[]
public android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View view, int layoutId) {
switch(layoutId) {
case com.xlh.app.R.layout.activity_databinding:
//调用实现ViewDataBinding类的bind方法
return com.xlh.app.databinding.ActivityDatabindingBinding.bind(view, bindingComponent);
//...
}
return null;
}
public static ActivityDatabindingBinding bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {
if (!"layout/activity_databinding_0".equals(view.getTag())) {
throw new RuntimeException("view tag isn't correct on view:" + view.getTag());
}
//返回自己的Activity对应layout文件生成的binding类对象
return new ActivityDatabindingBinding(bindingComponent, view);
}
private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;
private static final android.util.SparseIntArray sViewsWithIds;
//静态加载
static {
//7是布局文件中控件的个数
sIncludes = new android.databinding.ViewDataBinding.IncludedLayouts(7);
//...
sViewsWithIds = null;
}
//实现类databinding的构造方法...
public ActivityDatabindingBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
//调用super的构造
super(bindingComponent, root, 0);
//创建数组(数组长度由布局中View和ViewGroup的个数和决定)
final Object[] bindings = mapBindings(bindingComponent, root, 7, sIncludes, sViewsWithIds);
//...对布局中的子view初始化
setRootTag(root);
// listeners
invalidateAll();
}
protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
//根据子控件创建数组...并且给数组赋值
Object[] bindings = mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
//给对应布局中的控件赋值...
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView1 = (android.widget.TextView) bindings[1];
this.mboundView1.setTag(null);
//其他初始化操作...
return bindings;
}
七、原理分析
能解决什么问题?
1.减少findViewById的使用,提高了性能;
2.更新UI数据需要切换线程以及将数据分配给具体的view
如何解决这些问题?
总体思路:
针对没有layout布局,生成1个ViewdataBinding的类对象该对象持有对应布局需要展示的数据和布局中各个控件的引用;
同时该对象还有:将数据分解到各个view,在UI线程上更新数据,监控数据的变化,实时更新等功能;
<layout xmlns:android="http://schemas.android.com/apk/res/android" >
<data>
<!--此处定义该布局要用到的数据的名称及类型-->
</data>
<!--此处按照常规方式定义要使用的布局,其中可以使用binding表达式代表属性值,所谓binding表达式,指形如"@{user.firstName}"的表达式-->
</layout>
databinding布局文件模板...
背后实现:
1.对布局文件进行预处理...
databinding布局文件输入目录:
build/intermediates/data-binding-layout-out/debug/layout
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.xlh.databinding.DataBindingActivity"
android:tag="layout/activity_databinding_0" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
style="@style/textStyle"
android:tag="binding_1"
tools:text="userName"/>
<TextView
style="@style/textStyle"
android:tag="binding_2"
tools:text="userName"/>
<TextView
style="@style/textStyle"
android:tag="binding_3"
tools:text="userName"/>
</LinearLayout>
databinding会将原来的布局文件中的view设置一个tag标签...原来布局中的<data>标签和数据绑定的表达式会被单独存放;
databinding布局文件信息目录:
build/intermediates/data-binding-info/...该目录下存放所有的使用databinding布局的xml文件
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Layout
layout="activity_main"
modulePackage="com.xlh.app" absoluteFilePath="D:\App\app\src\main\res\layout\activity_databinding.xml"
directory="layout" isMerge="false">
<Variables declared="true" type="com.xlh.databinding.bean.User" name="user">
</Variables>
<Targets>
<Target tag="layout/activity_main_0" view="LinearLayout">
</Target>
<Target id="@+id/..." tag="binding_1" view="TextView">
<Expression text="user.firstName" attribute="android:text"/>
</Target>
<Target id="@+id/..." tag="binding_2" view="TextView">
<Expression text="user.nickName" attribute="android:text"/>
</Target>
<Target tag="binding_3" view="TextView">
<Expression text="user.email" attribute="android:text"/>
</Target>
</Targets>
</Layout>
注意:跟布局的tag是以Layout开头的...data标签中的数据基本原封不动的被加载到该文件中;
2.生成Activity...Binding类和BR资源文件
databinding会依据上面两个xml文件生成对应的两个类:
如果是MainActivity...生成ActivityMainBinding
其他Activity...DataBindingActivity则生成...ActivityDatabindingBinding...(生成规则自行归纳)
该类继承ViewDataBinding类
该类中的字段及属性:
//这里如果给控件定义了id,属性名称就是id,如果没有就是mboundView0..(++)
private final android.widget.LinearLayout mboundView0;
private final android.widget.TextView mboundView1;
private final android.widget.TextView mboundView2;
private final android.widget.TextView mboundView3;
private final android.widget.LinearLayout mboundView4;
// views
public final android.widget.TextView firstname;
public final android.widget.TextView lastname;
private final android.widget.LinearLayout mboundView0;
private final android.widget.ImageView mboundView3;
// variables
private com.like4hub.www.databindingtest.User mUser;
对应的view存在id,会生成1个public final 的view
对应View没有id,会存在mboundView...
BR文件:
package com.xlh.app;
public class BR {
public static final int _all = 0;
public static final int us = 1;
public static final int user = 2;
}
其中_all是默认生成,user变量就是生成对应类中的Binding中user变量...
public boolean setVariable(int variableId, Object variable) {
switch(variableId) {
case BR.user :
setUser((com.xlh.databinding.bean.User) variable);
return true;
}
return false;
}
public void setUser(com.xlh.databinding.bean.User User) {
this.mUser = User;
synchronized(this) {
mDirtyFlags |= 0x2L;
}
notifyPropertyChanged(BR.user);
super.requestRebind();
}
databinding不仅仅为xml中data标签中的variable生成对应的BR常量;
如果你在User类中的getName方法上添加@Bindable注解,
并且让User类继承BaserObservable那么,DataBinding生成的BR类中将会是这样:
public class User extends BaseObservable{
@Bindable
public String getName() {
return name;
}
}
public class BR {
public static final int _all = 0;
public static final int name = 1;
public static final int us = 2;
public static final int user = 3;
}
BR的常量是1个标识符,对应每一个数据的变化,当数据改变的时候,可以使用该标识符通知dataBinding,然后更新UI;
总结:<data>中的每一组variable都是可变的,所以databinding为它们生成BR标识符;
在数据类中的getXXX()方法上添加@Bindable注解,也能为某一具体属性添加BR标识符;
3.生成对应Activity的Bind对象并且绑定(这一个步骤3个过程)
a.渲染布局文件后,得到一个ViewGroup对象;
调用DataBindingUtil.setContentView的时候会获取android.R.layout.content作为root,即ViewGroup;
public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId,
DataBindingComponent bindingComponent) {
activity.setContentView(layoutId);
View decorView = activity.getWindow().getDecorView();
//将contentView作为ViewGroup传递进去
ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}
//...bindingComponent就是getDefaultComponent()方法返回的...
b.生成对应Activity的binding对象
通过DataBinderMapper.getDataBinder返回具体某Activity的Binding对象的bind方法
public android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View view, int layoutId) {
switch(layoutId) {
case com.xlh.app.R.layout.activity_databinding:
//返回对应binding对象
return com.xlh.app.databinding.ActivityDatabindingBinding.bind(view, bindingComponent);
}
return null;
}
public static ActivityDatabindingBinding bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {
if (!"layout/activity_databinding_0".equals(view.getTag())) {
throw new RuntimeException("view tag isn't correct on view:" + view.getTag());
}
//返回Activity的binding对象
return new ActivityDatabindingBinding(bindingComponent, view);
}
在构造方法中,ActivityMainBinding会首先遍历root,根据各个View的Tag或者id,初始化自己的fields;
fields就是views,该布局文件中的控件;
public ActivityDatabindingBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
super(bindingComponent, root, 3);
final Object[] bindings = mapBindings(bindingComponent, root, 7, sIncludes, sViewsWithIds);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView1 = (android.widget.TextView) 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);
// listeners
invalidateAll();
}
至此,Tag们的历史使命完成了,ActivityMainBinding将会把之前加到各个View上的Tags清空。
c.绑定数据
//绑定数据方法
invalidateAll() {
//...请求重新绑定
requestRebind();
}
requestRebind()方法为父类ViewDataBinding的方法, 子类没有重写;
调用父类的requestRebind()方法
/**
* @hide
*/
protected void requestRebind() {
if (mContainingBinding != null) {
mContainingBinding.requestRebind();
} else {
synchronized (this) {
if (mPendingRebind) {
return;
}
mPendingRebind = true;
}
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
//下一步
executePendingBindings();
}
}
public void executePendingBindings() {
if (mContainingBinding == null) {
//下一步
executeBindingsInternal();
} else {
//调用自己...
mContainingBinding.executePendingBindings();
}
}
private void executeBindingsInternal() {
if (mIsExecutingPendingBindings) {
//循环以上流程
requestRebind();
return;
}
//...省略
if (!mRebindHalted) {
//执行该抽象方法,此时执行到对应Activity生成的binding类的该方法
executeBindings();
//...
}
}
@Override
protected void executeBindings() {
//...
java.lang.String userName = null;
java.util.List<com.xlh.databinding.bean.User> us = mUs;
com.xlh.databinding.bean.User user = mUser;
// batch finished
if ((dirtyFlags & 0x32L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, userNameJavaLangObjectNullUserNickNameUserName);
android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userNameJavaLangObjectNullUserNickNameUserName);
}
if (user != null) {
// read user.nickName
userNickName = user.getNickName();
}
if (user != null) {
// read user.name
userName = user.getName();
}
if ((dirtyFlags & 0x22L) != 0) {
this.mboundView1.setOnClickListener(userClickNameAndroidViewViewOnClickListener);
this.mboundView2.setOnLongClickListener(userLongClickNickNameAndroidViewViewOnLongClickListener);
android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView3, userEmail);
}
}
如果给对应的数据类添加get方法,如:getAge...getAddress,那么在xml中的binding表达式中也可以使用
user.age或者user.address...因为源码中对该字段的赋值通过的是getXXX方法;
BindingAdapter:
android:text="@{user.name}"
//这里会比较之前的ui是否一致,如果不一致,就修改,一致直接return
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);
}
}
假如binding获取到user.name的值为"xielihuang",然后会通过bindingAdapter将该值赋值给
TextView的命名空间为android的text属性;
注解@BindingAdapter的参数“android:text”
方法第一个参数TextView
方法第二个参数CharSequence
当在TextView上设置text属性,且设置的值的类型是CharSequence时,就不要直接调用TextView相应的setText方法,
而是调用用户定义的这个BindingAdapter方法。
当在任意一个View的任意一个属性上使用binding表达式时,DataBinding框架的处理过程分成三步:
1、对binding表达式求值
2、寻找合适的BindingAdapter,如果找到,就调用它的方法
3、如果没有找到合适的BindingAdapter,就在View上寻找合适的方法调用
自定义BindingAdapter
DataBinding框架很开明,它承诺:在寻找合适的BindingAdapter时,会优先使用用户定义的BindingAdapter。
@BindingAdapter("android:text")
public static void updateText(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.
}
//下面这句代码,就是我们加进去的
CharSequence upperText = text.toUpperCase();
view.setText(upperText);
}
有了这个BindingAdapter,任何时候,任何情况下,只要我们在TextView的text属性上使用binding表达式,
并且这个表达式的值是CharSequence,那么,我们自定义的BindingAdapter就会被DataBinding框架调用;