先看两个问题:
问题1:android开发中,需要使用到findviewById来进行初始化view,如果页面,可能会看到几十行的findviewById方法,而如果需要设置点击事件,又要几十行的代码。
解决:ButterKnife,自动绑定了view,点击事件等,代替了繁杂的手写步骤。
问题2:用ButterKnife,就是如果一个页面view过多的话,也需要一长串的 @bindView 代码,导致一个页面轻轻松松300行+的代码量,看着也有点不舒服。而这些也都是重复性的工作,那么有什么办法解决吗?
解决:DataBinding!
MVVM
MVVM和MVP的区别,是把presenter层换成了viewmodel层,还有就是view层和viewmodel层是相互绑定的关系,这意味着当你更新viewmodel层的数据的时候,view层会相应的变动ui。视图和数据的双向绑定就是 通过Android Data Binding技术。
下面学习开始学习dataBinding。
一、配置databinding
首先在Module app下build.gradle中配置databinding
android {
dataBinding{
enabled = true
}
}
二、使用databinding
1、一个例子--简单使用
先写个Model实体类Man:
/**
* 侠客,ViewModel
*/
public class Man extends BaseObservable{
private String name;
private String level;
public Man(String name, String level) {
this.name = name;
this.level = level;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
}
然后布局文件:
<?xml version="1.0" encoding="utf-8"?>
<!--根标签-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!--变量的定义-->
<data>
<variable
name="man" <!--变量名-->
type="com.hfy.demo01.module.mvvm.bean.Man" /> <!--变量类型-->
</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="@{man.name}" /> <!--变量的引用-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{man.level}" />
</LinearLayout>
</layout>
说明:
a.布局根标签为:<layout>,由以前的<LinearLayout>改为<layout>,注意 l 小写。
b.定义变量的标签:<data>、<variable>,变量的引用:@{man.level}
最后在MvvmActivity中对 数据和视图 进行绑定。(要先Make Project)注意下面代码的注释1、2、3。
public class MvvmActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_mvvm);
//1、这里使用DataBindingUtil.setContentView()
//2、ActivityMvvmBinding是在写好<layout>布局后,make project,自动生成的Binding辅助类,包含了布局中所有的绑定关系
ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
//3、给 布局中定义的变量man 进行赋值。
Man man = new Man("段誉","爱情高手");
binding.setMan(man);
}
运行结果如下图。可见:我们没有findViewById、setText,依然可以把数据展示出来。
2、事件处理,两种方法如下图
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<!--对象变量的定义-->
<variable
name="man"
type="com.hfy.demo01.module.mvvm.bean.Man" />
<variable
name="onClickListener"
type="android.view.View.OnClickListener" />
</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="@{man.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{man.level}" /> <!--支持表达式-->
<!--事件处理的两种方法-->
<!--方法一、先设置控件id,然后java代码中使用Binding设置-->
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button1" />
<!--方法二、引用变量-->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{onClickListener}"
android:text="button2" />
</LinearLayout>
</layout>
//事件处理方法一,binding.
binding.btnTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
man.setName("段正淳");
}
});
//事件处理方法二:
binding.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MvvmActivity.this, "button2", Toast.LENGTH_SHORT).show();
}
});
3、布局属性
import用法,
<data>
<!--导入-->
<import type="com.hfy.demo01.module.mvvm.bean.Man" />
<!--对象变量的定义-->
<variable
name="man"
type="Man" />
</data>
变量定义,
<!--基本数据类型 的变量, java.lang.*会自动导入-->
<variable
name="home"
type="String" />
<variable
name="age"
type="int" />
<variable
name="isMale"
type="boolean" />
<!--集合变量的定义( <> 要用转义符 )-->
<variable
name="list"
type="ArrayList<String>" />
<!-------->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{home}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list.get(0)}" />
binding.setHome("安徽");
ArrayList<String> strings = new ArrayList<>();
strings.add("一");
strings.add("二");
binding.setList(strings);
静态方法调用,
<!--静态方法调用-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{Utils.getName(man)}" /> <!--要先导入Utils-->
public class Utils {
public static String getName(Man man) {
return man.getName();
}
}
Man man = new Man("段正淳","爱情高高高手");
binding.setMan(man);
支持表达式,(三目预算、+、!、&&、>= 等等)
<variable
name="isMale"
type="boolean" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{isMale? man.name + ":男的": man.name + ":女的"}' /> <!--支持表达式-->
Convert,把数据格式 转为需要的格式
<variable
name="time"
type="java.util.Date" />
public class Utils {
/**
* convertDate()这个方法在哪个类不重要,重要的是 @BindingConversion
* @param date
* @return
*/
@BindingConversion
public static String convertDate(Date date) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String format = simpleDateFormat.format(date);
return format;
}
}
ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
binding.setTime(new Date());
三、动态更新 / 双向绑定
此前例子中,Model实体类变化,UI是不会动态更新的。 接下来实现 动态更新:继承BaseObservable
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.hfy.demo01.module.mvvm.bean.Man" />
<variable
name="man"
type="Man" />
</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="@{man.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{man.level}" />
</LinearLayout>
</layout>
/**
* 侠客, 继承BaseObservable
*/
public class Man extends BaseObservable {
private String name;
private String level;
public Man(String name, String level) {
this.name = name;
this.level = level;
}
/**
* @return
* @Bindable BR中生成一个对应的字段,BR编译时生成,类似R文件
*/
@Bindable
public String getName() {
return name;
}
/**
* notifyPropertyChanged(BR.name),通知系统BR.name已发送变化,并更新UI
*
* @param name
*/
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
notifyPropertyChanged(BR.level);
}
}
ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
Man man = new Man("段誉","爱情高手");
binding.setMan(man);
// //事件处理方法一,binding.
binding.btnTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Model实体类发生变化 -- UI会动态变化
man.setName("段正淳");
}
});
接下来就是双向绑定:就是Model和View 通过VIewModel 进行双向动态更新。Model实体类变化,VIew会自动更新;View发生变化,Model实体类也能动态改变。
<!--使用EditText通过 "@={}" 改变man.name的值-->
<!--UI EditText变化,实体类man也动态变化-->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={man.name}"/>
五、结合RecyclerView
avtivity布局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--这里设置了id,就可以通过binding直接找到RecyclerView-->
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
</layout>
item布局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="man"
type="com.hfy.demo01.module.mvvm.bean.Man" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{man.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{man.level}" />
</LinearLayout>
</layout>
activity:
public class RecyclerActivity extends AppCompatActivity {
private ActivityRecyclerBinding binding;
/**
* launch MvpActivity
*/
public static void launch(Activity activity) {
Intent intent = new Intent(activity, RecyclerActivity.class);
activity.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_recycler);
// 1、使用 DataBindingUtil.setContentView() 代替 setContentView(R.layout.activity_recycler);
binding = DataBindingUtil.setContentView(this, R.layout.activity_recycler);
//直接通过binding找到了recyclerView
LinearLayoutManager layout = new LinearLayoutManager(this);
layout.setOrientation(LinearLayoutManager.VERTICAL);
binding.recycler.setLayoutManager(layout);
binding.recycler.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
ManAdapter manAdapter = new ManAdapter(getList());
binding.recycler.setAdapter(manAdapter);
}
private List<Man> getList() {
List<Man> list = new ArrayList<>();
list.add(new Man("乔峰", "帮主"));
list.add(new Man("虚竹", "小和尚"));
return list;
}
private class ManAdapter extends RecyclerView.Adapter<ManViewHolder> {
private final List<Man> mList;
public ManAdapter(List<Man> list) {
mList = list;
}
@NonNull
@Override
public ManViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
//2、这里是把item_layout对应的Binding 注入iewHolder(以前是View)
ItemLayoutRecyclerBinding manViewBinding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.item_layout_recycler, viewGroup, false);
return new ManViewHolder(manViewBinding);
}
@Override
public void onBindViewHolder(@NonNull ManViewHolder manViewHolder, int i) {
//3、绑定只用使用Binding,很简洁。
manViewHolder.manViewBinding.setMan(mList.get(i));
}
@Override
public int getItemCount() {
return mList.size();
}
}
private class ManViewHolder extends RecyclerView.ViewHolder {
private final ItemLayoutRecyclerBinding manViewBinding;
//4、这里没有findViewById()去找各个view。就holder了Binding。
public ManViewHolder(ItemLayoutRecyclerBinding manViewBinding) {
super(manViewBinding.getRoot());
this.manViewBinding = manViewBinding;
}
}
}
结果:
RecyclerView功能强大,使用DataBinding来处理RecyclerView Item 再合适不过,做到了数据和itemView的完美分离,告别了反复、冗余的自定义Adapter,不需要关心太多无意义的事。
六、使用注意点
1、尽可能少的variable和import。 过多的variable除了会让你多做几次无谓的绑定外,数据也将变得难以管理。
bad
<data>
<import type="com.ditclear.app.util.DateUtil"/>
<import type="android.view.View"/>
<import type="com.ditclear.app.util.StringUtil"/>
<import type="com.ditclear.app.network.model.StudentState"/>
<import type="java.math.BigDecimal"/>
<import type="com.ditclear.app.presentation.student.StudentActivity"/>
<variable
name="isShow"
type="Boolean"/>
<variable
name="time"
type="java.util.Date"/>
<variable
name="date"
type="java.util.Date"/>
<variable
name="signTime"
type="String"/>
<variable
name="item"
type="com.ditclear.app.network.model.student.StudentItem"/>
<variable
name="presenter"
type="com.ditclear.app.presentation.student.StudentActivity.Presenter"/>
</data>
better
<data>
<variable
name="vm"
type="com.ditclear.app.view.student.viewmodel.StudentViewModel"/>
</data>
数据的处理和view的显示都用ViewModel来处理,比如:
android:text="@{vm.signTime}"
android:visibility="@{vm.isShowVisbility}"
好处是减轻了layout.xml文件的复杂程度,xml文件只用来单纯的展示数据, 而复杂的逻辑判断都放在了ViewModel中,并且为页面布局数据提供了唯一的入口,方便查看和管理数据源。
2、避免使用复杂的表达式,下面这种的逻辑判断最好放在ViewModel中,由ViewModel提供对应的变量。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{isMale? man.name + ":男的": man.name + ":女的"}' /> <!--支持表达式-->
而且,刚入门DataBinding的人都遇到过找不到binding文件的错,可能是表达式写错、variable的类路径不对或者没import相应的类(比如View)等等,都与复杂的表达式有关系,而DataBinding报错也不太智能,有时不能准确定位,所以建议不要在xml里进行复杂的数据绑定,这些都尽量放到ViewModel里进行,xml只绑定简单的基础数据类型。
七、Android Studio插件 Databinding Support
https://plugins.jetbrains.com/plugin/9271-databinding-support
参考:
《Android进阶之光》
推荐阅读 详细系列文章: