概念
数据绑定,对MVVM的是实现,MVVM就是把MVC中的C层换成了VM
ViewModel也是一种Model,相对于Model更偏向业务和数据,而ViewModel仅仅用来展现,与View更紧密,把ViewModel绑定到xml,保证其数据来源都是来自于ViewModel,提高了开发效率,性能高功能强。
类似的方案
- ButterKnife
- Android Annotations
- RoboBinding
优势:
- 去除Activity/Fragment中的UI代码
- 不会因为id错而crash,减少findbyId的使用
- 保证执行在主线程
劣势:
- IDE支持不完善
- 报错信息不直接
- 没有重构支持
使用dataBinding
1.在app module - build.gradle 下添加
android {
……
dataBinding{
enabled = true;
}
}
2.对layout 文件改写
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.enjoylife.youyou.YouyouMainActivity">
<TextView
android:id="@+id/text_course"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
在layout最外层添加标签
3.绑定View
自动生成Binding类
//setContentView(R.layout.activity_youyou_main);
//由layout名称决定+后缀Binding
ActivityYouyouMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_youyou_main);
binding.textCourse.setText("English");
binding.buttonName.setText("nake");
把一个class绑定到一个layout
在刚才的layout中添加
<data>
<variable
name="student"
type="com.enjoylife.youyou.Student"/>
</data>
设置每个View 要绑定的数据
完整代码:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="student"
type="com.enjoylife.youyou.Student"/>
</data>
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.enjoylife.youyou.YouyouMainActivity">
<TextView
android:id="@+id/text_course"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{student.course}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{student.name}"/>
</LinearLayout>
</layout>
数据要用android:text=”@{student.name}”这种形式绑定
之后把activiry中的数据传进来
除了变量的绑定,还可以绑定事件:onClick/onLongClick/onTextChanged
绑定方法有俩种
- 方法关联(Method References), 像 OnClickListener 中的 onClick(View)等,方法完全与原始方法一致,单纯的点击事件;
- 监听器绑定(Listener Bindings),在 java 类中自定义方法名称,当事件发送后,用 lambda 判断调用的监听器,主要用来回传数据。
完整代码
public class YouyouMainActivity extends AppCompatActivity {
Student student = new Student("Nike","english");
ActivityYouyouMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(YouyouMainActivity.this,R.layout.activity_youyou_main);
binding.setStudent(student);
binding.setPresenter(new Presenter());
}
public class Presenter{
public void onTextChanged(CharSequence s, int start, int before, int count){
student.setName(s.toString());
binding.setStudent(student);
}
public void onClickFriend(View view){
Toast.makeText(YouyouMainActivity.this,"hi !!!",Toast.LENGTH_SHORT).show();
}
public void onClickListenerBinding(Student student){
Toast.makeText(YouyouMainActivity.this,"student name:" + student.name,Toast.LENGTH_SHORT).show();
}
}
}
layout代码
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="student"
type="com.enjoylife.youyou.Student"/>
<variable
name="presenter"
type="com.enjoylife.youyou.YouyouMainActivity.Presenter"/>
</data>
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onTextChanged="@{presenter.onTextChanged}"/>
<TextView
android:id="@+id/text_course"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{student.course}"/>
<Button
android:id="@+id/button_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{student.name}"
android:onClick="@{presenter.onClickFriend}"/>
<TextView
android:id="@+id/text_leve"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{student.course}"
android:onClick="@{() -> presenter.onClickListenerBinding(student)}"/>
</LinearLayout>
</layout>
还有一种形式:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
当在 ListView 或者 RecyclerView 的 adapter 中使用 data binding 时,也可能用到:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
以上格式都是省略了传入 onClick(android.view.View) 的参数的形式,Listener bindings 对于参数的形式提供了提供了俩种选择:要么省略,要么全命名;
android:onClick="@{(view) -> presenter.onClickListenerBinding(student)}"
java 代码:
public void onClickListenerBinding(View view,Student student){
Toast.makeText(YouyouMainActivity.this,"student name:" + student.name,Toast.LENGTH_SHORT).show();
}
Data binding在Activity中使用:
DataBindingUtil.setContentView(this, R.layout.activity_home);
Data binding在Fragment中使用:
DataBindingUtil.inflate(inflater, R.layout.homepage_fragment, container, false);
Data binding在activity中或者fragment加载其他布局怎么办呢?比如 popupwindow之类的
ItemMapInfoBinding infoBinding = ItemMapInfoBinding.inflate(getLayoutInflater());
View popupView = infoBinding.getRoot();
PopupWindow mPopupWindow = new PopupWindow(mContext);
mPopupWindow.setContentView(popupView);
避免复杂的监听器
监听器表达式使你的代码易于阅读,相反,复杂的表达式使布局难以阅读且不利于维护,这些表达式也使数据传给 UI 活着回调方法更简单,要记住,在唤醒的回调方法中实现业务逻辑。以下一些点击事件的处理不同于点击事件,他们要有一些属性:
布局细节
在标签中 import 一些类,可以使布局更易于与类映射,例如:
<data>
<import type="android.view.View"/>
</data>
要用到这个代码的位置:
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
当命名发生冲突的时候,要 “alias” 以区别,根据情景进行区别使用哪个类:
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
也可以 import 一些类,其中的静态 static 代码块和方法可能在表达式中用到:
<data>
<import type="com.example.MyStringUtils"/>
<variable name="user" type="com.example.User"/>
</data>
…
<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
变量 Variables 的使用
在 data 部分可能用到大量的变量,每个变量都可能在布局的表示中用到:
<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>
如果这些变量实现 implements Observable or is an observable collection 则要被反射,否则 class or interface that does not implement the Observable* interface 这些变量不能被注意 observed。
When there are different layout files for various configurations (e.g. landscape or portrait), the variables will be combined. There must not be conflicting variable definitions between these layout files.
The generated binding class will have a setter and getter for each of the described variables. The variables will take the default Java values until the setter is called — null for reference types, 0 for int, false for boolean, etc.
A special variable named context is generated for use in binding expressions as needed. The value for context is the Context from the root View’s getContext(). The context variable will be overridden by an explicit variable declaration with that name.
###绑定类名要规范
- Binding class is generated based on the name of the layout file, starting it with upper-case, removing underscores ( _ ) and capitalizing the following letter and then suffixing “Binding”.
<data class="ContactItem">
...
</data>
- If the class should be generated in a different package within the module package, it may be prefixed with “.”:
<data class=".ContactItem">
...
</data>
主要性能
- 0反射
- findViewById 需要遍历整个viewgroup,data binding只做一次解析,把所有 view 放在 Object 数组中
- 使用位标记检验更新, 根据每个view 相关的mDirtyFlags 值判断数据的变化,作出相应显示。
- 数据改变在下次批量更新才会触发操作
- 缓存表达式
表达式
include
Observable Objects
private static class User extends **BaseObservable** {
private String firstName;
private String lastName;
**@Bindable**
public String getFirstName() {
return this.firstName;
}
**@Bindable**
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
**notifyPropertyChanged(BR.firstName);**
}
public void setLastName(String lastName) {
this.lastName = lastName;
**notifyPropertyChanged(BR.lastName);**
}
}
ObservableFields
少量的数据更改
private static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
user.firstName.set("Google");
int age = user.age.get();
Observable Collections
ObservableArrayMap、ObservableArrayList
高级绑定:动态变量
- RecyclerView
- onBindViewHolder
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
- 立即绑定,执行executePendingBindings()
- 后台线程,collection 不能同步
自定义属性
- 自动寻找set方法
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrimColor}">
</android.support.v4.widget.DrawerLayout>
通过 app:scrimColor=”@{@color/scrimColor}” 自动匹配
app:scrimColor=”@{@color/scrimColor}”
- BindingMethods
@BindingMethods({
@BindingMethod(
type = "android.widget.ImageView",
atttibute = "android:tint",
method = "setImageTintList",
)})
绑定 android:tint -> setImageTintList()
- Binding适配器
//单个ImageView 绑定uri
@BindingAdapter("bind:imageUri")
public static void loadImageFromUri(ImageView image,Uri uri){
}
//绑定多个属性
@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
Converters (binding 转换)
- 例如binding 表达式返回的的 Object 转换为String 类型
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
- 自定义 Conversions
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
background一般用的是 Drawable 形式,要用 color 则进行转换:
//转换为set需要的属性
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
双向绑定
@= 用来双向绑定
实现原理:
InverseBindingListener 中的 onChange()
以下是调用地:
TextViewBindingAdapter.setTextWatcher(this.name, (BeforeTextChanged)null, (OnTextChanged)null, (AfterTextChanged)null, this.nameandroidTextAttrChanged);
formModel.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable sender, int propertyId) {//propertyId 是映射到的BR的值
Toast.makeText(TwoWayActivity.this,"! ------> " + propertyId,Toast.LENGTH_SHORT).show();
}
});
参考官方文档:
https://developer.android.google.cn/topic/libraries/data-binding/index.html#layout_details