最近做完了新项目,里面用到了, lifecycle, Android Data Binding, mvvm
基础的只是就不复制黏贴了,网上太多了, 下面只总结经验
其中有几篇讲的很细的,还有其他的也可以留着查
https://blog.csdn.net/jjwwmlp456/article/details/54915981 Android Data Binding Library 官方文档(译)
https://blog.csdn.net/yzy1226466341/article/category/6703506 Data Binding
https://blog.csdn.net/qiang_xi/article/details/75379321 Data Binding 1 2 3 4
https://blog.csdn.net/qq_27630169/article/details/52595913 转换
https://blog.csdn.net/qq_33689414/article/details/52205718
一、Data Binding Library (数据绑定库),旨在减少绑定应用程序逻辑和布局所需的一些耦合性代码
最低支持Android 2.1 (API Level 7)
构建环境
Data binding只支持Android Studio的开发
Android Studio的版本必须在1.3版本及以上,BuildToolsVersion必须在22.0.1以上。
以上条件达到以后,只需再项目的build.gradle文件中的android节点下增加以下代码使项目支持Databinding,这样的话就可以使用这个框架了。
使用gradle插件1.5-alpha1以上
在build.gradle添加如下声明
android {
....
dataBinding {
enabled = true
}
}
为什么配置了 dataBinding{enabled = true}之后就可以使用dataBinding方式进行开发了?
在Android Studio中是依靠gradle来管理项目的,在创建一个项目时,从开始创建一直到创建完毕,整个过程是需要执行很多个gradle task的,
这些task有很多是系统预先帮我们定义好的,比如build task,clean task等,DataBinding相关的task也是系统预先帮我们定义好的,
但是默认情况下,DataBinding相关的task在task列表中是没有的,因为我们没有开启dataBinding,但是一旦我们通过 dataBinding{enabled = true}的方式开启DataBinding之后,
DataBinding相关的task就会出现在task列表中,每当我们执行编译之类的操作时,就会执行这些dataBinding Task, 这些task的作用就是检查并生成相关dataBinding代码,
比如dataBindingExportBuildInfoDebug这个task就是用来导出debug模式下的build信息的。
项目中所有的task列表可以从Android Studio面板右侧贴边的Gradle中找到。
二、工作原理
DataBinding开发实际上与原始的Android开发没有过多的区别,只是极大的简化了代码对控件的操作,
允许程序员直接在布局上进行数据的赋值,我们只需要在代码中对布局中的文件变量进行赋值操作,操作之后界面便会实现相应的改变。
我们需要做的改变只是将布局文件的根布局改为,layout节点下有data和真正的layout节点(也就是LinearLayout等布局方式),
Android Studio为什么有能力生成DataBinding相关代码
我们知道Android Studio可以自动生成DataBinding相关代码。
在以前,如果我们想使用dataBinding方式开发,还需要引入dataBinding的相关Library,但是现在我们不用引入这些library了,因为在我们配置完dataBinding{enabled = true}之后,系统会自动引入这些library,但是这些library的源码在哪里放着呢?我们可以找到Android Studio的安装路径,在\plugins\android\lib路径下找到data-binding.jar,比如我的路径则为:D:\Android\AndroidStudio\plugins\android\lib,这个jar包中存放的就是DataBinding相关的源码,包括annotationprocessor,parser,tool,以及相关注解和类,这个源码就是让Android Studio有能力生成DataBinding相关代码的原因。
但是我们发现这些源码都已经被编译为class文件,想查看真正的源码还想看到对应的注释通过这个源码是不可能了。我们可以通过这个地址DataBinding的java源码地址 查询和下载DataBinding各个版本的源码和Javadoc。
三.使用方法
DataBinding总共有如下几个标签可以使用,一些标签下还有一些属性可以更具体的控制这些标签:
- layout标签
- data标签:class属性
- variable标签:type属性、name属性
- import标签:type属性、alias属性
这些标签以及属性的定义和使用都可以在compilerCommon
这个jar包中的android/databinding/tool/store/LayoutFileParser.java这个类中找到。
(1) 自定义的监听事件
//根据实际需求,填写方法需要的参数
public class ViewActionListener {
//无返回值
public void testClick(){}
public void testClick(View view){}
public void testClick(View view ,name, age ){} // view 这个参数可以带也可以不带,不过一般都是需要带上的
//有返回值
public boolean buttonClick(View view ){}
}
(2) @{} 为单向的绑定 , @ ={} 为双向的绑定
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type ="android.view.View"> //导入类
<import type ="com.example.my.View" alias="renameView"/> //类名都是View(上面也是View) 可以重命名
<import type ="com.example.MyUtils"/> //导入工具类
<variable name="testInt" type="int"/> //定义常量
<variable name="testList" type="List<User>"/> //引入list
<variable name="testMap" type="Map<String, String>"/> //引入map
<variable name="testBean" type="com.example.TestBean"/> //引入实体类
<variable name="testModel" type="com.example.MainModel"/> //引入model
<variable name="testAdapter" type="com.example.MainAdapter"/> //引入adapter
<variable name="testActivity" type="com.example.MainActivity"/> //引入Activity
<variable name="testFragment" type="com.example.MainFragment"/> //引入fragment
<variable name="listener" type="com.example.ViewActionListener"/> //引入listener
</data>
//对 include的布局传递数据
<include layout="@layout/tow_layout"
app:data="@{testList}"/>
<TextView
android:id="@+id/m_test1"
/*** 赋值 ***/
android:text="@{testClass.data}" //设置实体类的值
android:text="@{testList[index]}" //设置list的值 testList[testActivity.num]
android:margin ="@{testBean.padding ? @dimen/padding : @dimen/no}"
android:text="@{` lalal `+ MyUtils.rename()}" //字符串拼接
android:text='@{String.valueOf( 10 + (Integer)testList["age"] + (Integer) testBean.age )}'
android:text="@{MyUtils.rename()}" //静态的属性方法直接使用
//合并null操作符,使用 “??” 操作符
android:text="@{testBean.Name ?? testBean.age}" //等于 android:text="@{testBean.Name == null ?? testBean.age: testBean.Name}"
android:text="@{ @string /Stringneddvalue(firstName, lastName)}" //给string动态赋值显示
android:color="@{ @string /Stringneddvalue(firstName, lastName)}" //设置color
android:visibile="@{ if (testActivity.show ? View.Visibile : View.GONE )}" //view 是否显示
android:text="@{user.firstName, default=PLACEHOLDER}" //设置default值
android:text="@{StringUtils.show(context,entry.text)}" //引用 context
/*** 设置listener ***/
android:onClick="@{listener::testClick}"
android:onClick="@{(view) -> listener.testClick(view,testInt,testActivity.name)}"
// 比如 null for reference types, 0 for int, false for boolea, void
android:onClick="@{(view) -> view.isVisible() ? doSomething() : void}"
/*** 双向绑定,更改实体类中的数据
a nd roid :text=”@ ={userInfo.name}”就是实现的 一种双向的绑定, Activity值改变会刷新数据,EditText输入文本以后会改变Activity对应的实体,
如此形成绑定以后则无需再关心layout中的标签了。 布局中的id与na me的绑定就是数据的单向绑定与双向绑 定 , @{ } 为单向的绑定 ,@ ={}为双向的绑定。
***/
android:text="@={userInfo.name}"
/>
//可以在表达式中直接引用带 id 的 view,引用时采用驼峰命名法
<TextView
android:text="@{user.entry}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{mTest1.getVisibility() == View.GONE ? View.GONE : View.VISIBLE}" />
//这里TextView直接引用第一个TextView的id
</layout>
java 代码中的一些操作
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
//model 生成 并关联
binding.setTestModel(mainModel)
//生成数据并绑定
binding.setTestBean(new TestBean)
//根据id 塞入数据
binding.setVariable(BR.testInt, 2);
//如果普通数据,刷新imageUrl的value
binding.notifyPropertyChanged(BR.imageUrl);
//activity 或 fragment并关联
binding.setTestActivity(this);
binding.setTestFragment(this);
// listener 关联
binding.setListener(this);
//获取 contentView
binding.getRoot();
//获取控件对象,直接操作
binding.textView.setText();
binding.testClass = xxxx;
Binding,ActivityMainBinding这个类从哪来的在MainActivity我们先不看别的,我们看见了一个ActivityMainBinding的类,看这个类名就大概能猜到这个类是与data binding框架相关的,
但是究竟是何种关系呢?实际上呢,ActivityMainBinding就是根据layout文件编译生成的,也就相当于一个layout转化的一个Java类,
(1)但是如果你在实际编写代码的过程中,你会发现并没有执行编译、运行之类等操作,ActivityMainBinding这个类就直接能用了,竟然还有这种操作?其实是Android Studio 这个IDE自动帮我们做了这一步,在默认情况下,系统会使用Android Studio为我们自动生成databinding相关的代码,
但是这种方式生成的代码不能调试,如果你想通过点击ActivityMainBinding跳转到它的源码中,你会发现并不能如你所愿,而是会跳转到对应的布局文件中。
那么如果我们确实要查看ActivityMainBinding的源码并且还想调试,我们就需要通过另外一种方式:手动编译代码。这两种方式可以通过Android Studio的设置面板修改。
settings -> editor -> data binding 有相关设置
(2)下面我们来看看它的转换规则。
首先我们看看它的命名规则,这个我们新建以后自动生成的layout文件叫做activity_main.xml,而我们自动生成的Binding类叫ActivityMainBinding,
生成的规则是以下划线作为分割线,首字母变成大写,以Binding结尾,Binding类的存放是在app\build\generated\source\apt\debug\com\wshuttle\trailerplatform\databinding的路径下的。
我们再看看Binding类内部的生成规则。Binding类会对在layout中data节点下定义的变量都生成一个私有的变量值,并为它生成get和set的方法,
方便我们直接对变量进行赋值;会对layout中有id的控件生成一个public的变量,可如同代码中bing.listView获取layout的listview,并且控件的类型获取时已经确定了,
不再需要强制转换等任何的操作。
View中使用id
在布局中的每个使用了id的view,都会在binding class中创建出一个同名的public属性。这种绑定机制,比findViewById更快速。例:
<TextView
android:id="@+id/lastName
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
/>
在binding class中将会生成:
public final TextView lastName;
Variable
将为每个variable生成getter和setter方法。如
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>
上面的代码将生成:
public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);
自定义绑定类名(一般用不到)
自定义绑定类名
默认情况下,binding 类的名称取决于布局文件的命名,以大写字母开头,移除下划线,后续字母大写并追加 “Binding” 结尾。
这个类会被放置在 databinding 包中。举个例子,布局文件 activity_main.xml 会生成 ActivityMainBinding 类。
如果 module 包名为 com.example.my.app ,binding 类会被放在 com.example.my.app.databinding 中。
通过修改 data 标签中的 class 属性,可以修改 Binding 类的命名与位置。举个例子:
<data class="CustomBinding">
...
</data>
以上会在 databinding 包中生成名为 CustomBinding 的 binding 类。如果需要放置在不同的包下,可以在前面加 “.”:
<data class=".CustomBinding">
...
</data>
这样的话, CustomBinding 会直接生成在 module 包下。如果提供完整的包名,binding 类可以放置在任何包名中:
<data class="com.example.CustomBinding">
...
</data>
使用注意事项
表达式语言
表达式语言与 Java 表达式有很多相似之处。下面是相同之处:
数学计算 + - / * %
字符串连接 +
逻辑 && ||
二进制 & | ^
一元 + - ! ~
位移 >> >>> <<
比较 == > < >= <=
instanceof
组 ()
字面量 - 字符,字符串,数字, null
类型转换
函数调用
字段存取
数组存取 []
三元运算符 ?:
描述 转义字符 十进制 显示结果
空格 ; ;
小于号 <; <; <
大于号 >; >; >
与号 &; &; &
引号 "; "; "
撇号 &apos; '; '
乘号 ×; ×; ×
除号 ÷; ÷; ÷
不支持的操作符
一些 Java 中的操作符在表达式语法中不能使用。
this
super
new
显示泛型调用<T>
四、 交互 activity与layout的交互(PS: 如果不定义var 变量,但是就必须每次改变值都调用bing.set的方法)
public ObservableBoolean show = new ObservableBoolean();
public ObservableInt imageUrl = new ObservableInt(R.mipmap.ic_launcher);
private OrderInfo orderInfo;
..
...
观察对象,观察字段和观察集合。(上面实际用到了数据的赋值, 但是当数据变化时,如何自动赋值过去)
(1)Observable Objects
A class implementing the Observable interface ,将被允许附加一个listener,来监听对象所有属性的改变
Observable interface 有一个机制来添加和删除listeners,但需要通知开发者。为了让开发变得更容易,
提供一个基类,BaseObservable,它实现创建listener的注册机制。当属性数据改变时,数据类实现者需要响应通知。
这是通过 在get ter上使用 一个注解 @Bindable,并在setter中进行通知。
private class TestBean extends BaseObservable {
private String name;
public Test(){
}
public Test(String name){
this.name = name;
}
@Bindable
public String getName() {
return this.firstName;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
}
@Bindable 在编译时将生成一个BR class 。BR类文件将生成在moudle的package下。如果数据类的基类不能被改变,
Observable interface的实现类可以使用PropertyChangeRegistry存储和有效地通知listeners。
public class TestBeanField{
public ObservableInt id = new ObservableInt();
public ObservableField<String> name = new ObservableField<String>();
public UserInfo(int id, String name) {
this.id.set(id);
this.name.set(name);
}
}
(2)观察属性(ObservableFields)
使用ObservableField 和它的同级的一些类型:
ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt,
ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable。
(3) 观察集合(Observable Collections)
ObservableArrayMap<String, Object> mObservableArrayMap = new ObservableArrayMap<>();
mObservableArrayMap.put(xx,xx);
ObservableArrayList<Object> mObservableArrayList = new ObservableArrayList<>();
<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap<String, Object>"/>
</data>
//view model 中的定义
public class MainModel{
public BaseObservable mBaseObservable = new TestBean();
public ObservableField<String> firstName = new ObservableField<>()
public ObservableList<List> firstName = new ObservableList<>()
public final ObservableInt age = new ObservableInt();
}
生成的绑定类,会链接布局的variables。绑定的名称和包可能是自定义的的(生成的所有绑定类都 extends ViewDataBinding)
生成
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
单独绑定
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
高级绑定
动态Variable
有时,特定的绑定类不会为人所知。例如,一个RecyclerView.Adapter操作的任意布局,不知其特定的绑定类。仍需要通过onBindViewHolder(VH, int)来绑定值。
在下面的例子中,RecyclerView中的所有布局,都绑定了一个”item”,有一个BindingHolder#getBinding() 返回ViewDataBinding 类型
直接绑定
当一个variable or observable发生了改变,绑定框架会安排在下一帧进行视图的改变。
然而,有时希望立即发生改变。可以使用executePendingBindings()来强制执行
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
//xml 中的 item 塞入值
holder.getBinding().setVariable(BR.item, item);
//强制执行
holder.getBinding().executePendingBindings();
}
Attribute Setters (主要是 注解相关知识)
当view使用了绑定表达式,只要绑定值发生变化,生成的绑定类必须调用相应的setter方法。定制数据绑定框架的方式方法调用设置值。数据绑定框架允许自定义setter方法。
(1)自动Setters + 自定义Setters( 自定义属性 )
自动Setters
对于一个attribute,数据绑定框架将试图查找对应的setAttribute()。它的namespace并不重要,只关注attribute的name。
如,TextView中的属性android:text使用了表达式,那么框架就会查找setText(String)。如果表达式返回的是int,那么将会查找setText(int)。
所以,表达式需要返回正确的类型。数据绑定框架,支持创建一个布局元素(View|ViewGroup)中,并不存在的属性( 用于自定义属性)。
如下,生成的binding class中,将生成一个setDrawerListener(listener):
自定义Setters
一些属性需要自定义绑定逻辑。例如,属性android:paddingLeft没有对应的setter方法,
而在view中有一个方法为setPadding(left, top, right, bottom)
可以使用@ BindingAdapter来自定义一个关于属性android:paddingLeft的setter。
@BindAdapter这个相当于注解这个方法是一个自定的属性;{}内部表示使用时这个属性的名字,比如说@BindAdapter(“{image_url}”),
在xml中可以这样子使用app:image_url=”url”,注意这些自定属性是在http://schemas.android.com/apk/res-auto这个命名空间下的(也就是data binding的空间下),
记得声明;声明自定义属性的方法名是不限制的,遵从Java方法的规范,可以重载等,data binding会针对对应使用的地方,
找到相匹配的方法进行设置;方法的参数,第一个必须是View的子类,第二个也就是需要传进来的数据,可以是任意的数据类型,在xml中也可以用databinding的实体传递进来。
例如:
重命名 Setters
有一些属性的 setter方法, 与它的属性名不匹配。 这些属性的setter可能会 通 过 使用 @BindingMethods,
来进行重命名。 要求内问包 含 @BindingMethod。 例如ImageView有一 个属性android:tint,它对应setter方法为setImageTintList(ColorStateList tint):
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
开发人员将不太可能需要重命名setter,因android属性框架已经实现。
自定义Setters
<layout http://schemas.android.com/apk/res-auto
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"
/>
<com.test.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"
/>
/>
创建binding类(自定义Setters)
public class MyViewBinding{
/** 自定义自己的属性,网上copy的 **/
@BindingAdapter({"bind:name", "bind:age"}) //看到网上有bind:name 这种,不过没用过
public static void myNameAge(View view, String name,String age) {
view.setText();
}
@BindingAdapter({"image_url"})
public static void loadImage(ImageView view, String url){
if(!StringUtils.isEmpty(url)) {
Glide.with(view.getContext()).load(url).into(view); //设置全缓存.diskCacheStrategy(DiskCacheStrategy.ALL)
}
}
@BindingAdapter({"image_url"})
public static void loadImage(ImageView view, int resoureId){
view.setImageResource(resoureId);
}
@BindingAdapter({"image_url"})
public static void loadImage(DraweeView view, String url){
view.setController(Fresco.newDraweeControllerBuilder().setUri(url).build());
}
@BindingAdapter({"weight"})
public static void setWeight(View view, int weight){
if(weight < 0 ){
weight = 0 ;
}
view.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT,
weight));
}
@BindingAdapter({"imageWidth"})
public static void setImageWidth(ImageView view, float size){
ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = UIUtils.dp2Px(size);
view.setLayoutParams(params);
}
@BindingAdapter({"imageHeight"})
public static void setImageHeight(ImageView view, float size){
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = UIUtils.dp2Px(size);
view.setLayoutParams(params);
}
@BindingAdapter({"RLMargins"})
public static void setRLMargins(View view, MarginInfo marginInfo){
if (marginInfo == null){
return;
}
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( ViewGroup.LayoutParams
.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(marginInfo.getLeft(),marginInfo.getTop(),marginInfo.getRight(),
marginInfo.getBottom());
view.setLayoutParams(layoutParams);
}
@BindingAdapter({"LLMargins"})
public static void setLLMargins(View view, MarginInfo marginInfo){
if (marginInfo == null){
return;
}
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams
.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(marginInfo.getLeft(),marginInfo.getTop(),marginInfo.getRight(),
marginInfo.getBottom());
view.setLayoutParams(layoutParams);
}
//自定义布局绑定(可用于listview等)
@BindingAdapter({"bind:itemView")
public static void myNameAge(ListView view, itemBinder<T> itemViewWarp) {
Adapter aa = new Adapter(itemViewWarp)
view.setAdapetr(aa);
}
/** 对系统的 paddingLeft 进行自己转换操作 **/
@BindingAdapter("android:paddingLeft")
public static void myName(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
}
转换器
对象转换
当从一个绑定表达式返回一个对象,就会有一个setter被采用。
该对象将会被转换成setter中的参数类型。
下面的例子,通过ObservableMaps来保存数据:
userMap返回一个对象,该对象将自动转换成setText(CharSequence)中的参数类型。
可能会有混乱的参数类型,开发人员需要在表达式中显式cast
<TextView
android:text='@{String.valueOf(userMap["lastName"])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
自定义转换
有时特定类型之间会自动转换。例如,设置背景:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
这里,background的setter的参数类型应该是一个drawable,但color是一个整数。
应当有一个转换规则,将int color转换为Col orDrawable。
这种转换是通过使用一个 @BindingConversion的静态方法来实现的:
//此方法默认已实现,所以可以默认不用管
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
注意,转换只发生在setter时期。所以不允许混合类型,如:
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
...