一、Databinding使用的优势
1.1,实现xml的绑定,去除id的绑定操作;
1.2,将自定义控件很方便的全局注入xml,比如xml中的head。并实现对应逻辑的统一注入,比如点击返回销毁当前页面;
1.3,另外提供全局点击事件的单点操作,防止过快点击产生多次打开同一个页面的情况产生。
二、使用方法
2.1,在需要用的module中配置
android {
...
dataBinding {
enabled = true
}
}
即可
2.2,使用 Data Binding 之后,xml的布局文件就不再单纯地展示 UI 元素,还需要定义 UI 元素用到的变量。所以,它的根节点不再是一个 ViewGroup,而是变成了 layout,并且新增了一个节点 data。
2.3,那么如何实现绑定?
one:java类中:
activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//one
userBean=new UserBean("zhanglei","18 years old");
activityMainBinding.setUser(userBean);
xml布局中:(data里面绑定实体类后)
android:text="@{user.name}"
实体类中:
public class UserBean { private String name; private String yearsOld; public UserBean(String name, String yearsOld) { this.name = name; this.yearsOld = yearsOld; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getYearsOld() { return yearsOld; } public void setYearsOld(String yearsOld) { this.yearsOld = yearsOld; } }
two:java类中:
activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//two
private UserField userField = new UserField();
userField.realName.set("Chiclaim");
userField.mobile.set("119");
binding.setFields(userField);
xml布局中:(data里面绑定实体类后)
android:text="@{fields.realName}"
实体类中:
public class UserField { public final ObservableField realName = new ObservableField<>(); public final ObservableField mobile = new ObservableField<>(); }
three:java类中:
activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//three
private ObservableArrayMap map = new ObservableArrayMap();
map.put("realName", "Chiclaim");
map.put("mobile", "119");
binding.setCollection(map);
xml布局中:
data里面绑定实体类后
android:text="@{collection[`mobile`]}"
实体类中:无
三、自定义控件的全局注入
3.1,这里首先提供一个防止快速点击,产生多响应事件的抽象类
/**
* 功能:防止快速点击,产生多响应事件
*/
public abstract class OnClickEvent implements View.OnClickListener {
public long lastTime;
public long delayTime = 500;
public final static long longDelayTime = 1000;
public abstract void singleClick(View v);
public OnClickEvent() {
}
public OnClickEvent(boolean isLongTime) {
if (isLongTime) {
delayTime = longDelayTime;
}
}
@Override
public void onClick(View v) {
if (onDoubClick()) {
return;
}
singleClick(v);
}
public boolean onDoubClick() {
boolean flag = false;
long time = System.currentTimeMillis() - lastTime;
if (time < delayTime) {
flag = true;
}
lastTime = System.currentTimeMillis();
return flag;
}
}
3.2,继承对应布局控件,将代码DataBindingUtil提供的方法注入即可,代码如下
public class BaseHeader extends FrameLayout{ private BaseHeaderBinding mBinding; public BaseHeader(@NonNull Context context) { super(context); initView(context); } public BaseHeader(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(context); } public BaseHeader(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public BaseHeader(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context); } private void initView(Context context){ LayoutInflater inflater= (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mBinding= DataBindingUtil.inflate(inflater,R.layout.base_header,null,false); mBinding.tvBack.setOnClickListener(new OnClickEvent() { @Override public void singleClick(View v) { ((Activity)v.getContext()).finish(); } }); addView(mBinding.getRoot()); } }
四、关于双向绑定几点注意事项
4.1,现在假设一种情况,当你更换成EditText时,如果你的用户名User.name已经绑定到EditText中,当用户输入文字的时候,你原来的user.name数据并没有同步改动。
这时候我们就要将正向绑定改成双向绑定
属于正向绑定的写法:android:text="@{user.name}"
属于双向绑定的写法:android:text="@={user.name}"
看出微小的差别了吗?对,就是"@{}"改成了"@={}",是不是很简单?
4.2,隐式引用属性
同样你也可以在别的View上引用属性:
当CheckBox的状态发生改变的时候,ImageView也会同时发生改变。在复杂情况下,这个特性没什么卵用,因为逻辑部分我们是不建议写在XML中。
4.3,目前Android支持双向绑定的控件
AbsListView android:selectedItemPosition
CalendarView android:date
CompoundButton android:checked
DatePicker android:year, android:month, android:day
NumberPicker android:value
RadioGroup android:checkedButton
RatingBar android:rating
SeekBar android:progress
TabHost android:currentTab (估计没人用)
TextView android:text
TimePicker android:hour, android
4.4,自定义双向绑定
设想一下我们使用了下拉刷新SwipeRefreshLayout
控件,这个时候我们希望在加载数据的时候能控制refreshing的状态,所以我们加入了ObservableBoolean的变量swipeRefreshViewRefreshing
来正向绑定数据,并且能够在用户手动下拉刷新的时候同步更新swipeRefreshViewRefreshing数据:
// SwipeRefreshLayout.java
public class SwipeRefreshLayout extends View {
private boolean isRefreshing;
public void setRefreshing() {/* ... */}
public boolean isRefreshing() {/* ... */}
public void setOnRefreshListener(OnRefreshListener listener) {
/* ... */
}
public interface OnRefreshListener {
void onRefresh();
}
}
接下来我们需要告诉框架,我们需要将SwipeRefreshLayout
的isRefreshing的值反向绑定到swipeRefreshViewRefreshing
:
@InverseBindingMethods({
@InverseBindingMethod(
type = android.support.v4.widget.SwipeRefreshLayout.class,
attribute = "refreshing",
event = "refreshingAttrChanged",
method = "isRefreshing")})
这是一种简单的定义,其中event和method都不是必须的,因为系统会自动生成,写出来是为了更好地了解如何绑定的,可以参考官方文档InverseBindingMethod。
当然你也可以使用另外一种写法,并且如果你的值并不是直接对应Observable的值的时候,就可以在这里进行转换:
@InverseBindingAdapter(attribute = "refreshing", event = "refreshingAttrChanged")
public static boolean isRefreshing(SwipeRefreshLayout view) {
return view.isRefreshing();
}
上面的event同样也不是必须的。以上的定义都是为了让我们能够在布局文件中使用"@={}"这个双向绑定的特性。接下来你需要告诉框架如何处理refreshingAttrChanged事件,就像处理一般的监听事件一样:
@BindingAdapter("refreshingAttrChanged")
public static void setOnRefreshListener(final SwipeRefreshLayout view,
final InverseBindingListener refreshingAttrChanged) {
if (refreshingAttrChanged == null) {
view.setOnRefreshListener(null);
} else {
view.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh() {
colorChange.onChange();
}
});
}
}
一般情况下,我们都需要设置正常的OnRefreshListener,所以我们可以合并写成:
@BindingAdapter(value = {"onRefreshListener", "refreshingAttrChanged"}, requireAll = false)
public static void setOnRefreshListener(final SwipeRefreshLayout view,
final OnRefreshListener listener,
final InverseBindingListener refreshingAttrChanged) {
OnRefreshListener newValue = new OnRefreshListener() {
@Override
public void onRefresh() {
if (listener != null) {
listener.onRefresh();
}
if (refreshingAttrChanged != null) {
refreshingAttrChanged.onChange();
}
}
};
OnRefreshListener oldValue = ListenerUtil.trackListener(view, newValue, R.id.onRefreshListener);
if (oldValue != null) {
view.setOnRefreshListener(null);
}
view.setOnRefreshListener(newValue);
}
现在我们终于可以使用双向绑定的技术啦。但是要注意,需要设置requireAll = false,否则系统将识别不了refreshingAttrChanged属性,前文提到的文章例子里并没有设置这个。
在ViewModel中,我们的数据是这样的:
// MyViewModel.java
public final ObservableBoolean swipeRefreshViewRefreshing = new ObservableBoolean(false);
public void load() {
swipeRefreshViewRefreshing.set(true);
// 网络请求
....
swipeRefreshViewRefreshing.set(false);
}
public SwipeRefreshLayout.OnRefreshListener onRefreshListener() {
return new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
// Do something you need
}
};
}
在布局文件中是这样设置的:
...
最后我们还有一个小问题,就是双向绑定有可能会出现死循环,因为当你通过Listener反向设置数据时,数据也会再次发送事件给View。所以我们需要在设置一下避免死循环:
@BindingAdapter("refreshing")
public static void setRefreshing(SwipeRefreshLayout view, boolean refreshing) {
if (refreshing != view.isRefreshing()) {
view.setRefreshing(refreshing);
}
}
参考网址:http://www.jb51.net/article/126566.htm,https://github.com/chiclaim/awesome-android-mvvm
猛戳这里demo下载
五、Databinding布局绑定框架
猛戳这里布局绑定demo下载