1.基本使用:
开启DataBinding:
android {
...
dataBinding {
enabled = true
}
}
databinding布局的一般写法:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View" />
<variable
name="viewmodel"
type="com.example.mtest.MainViewModel" />
<variable
name="callback"
type="android.view.View.OnClickListener" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{callback}"
android:text="@{viewmodel.name}"/>
</FrameLayout>
</layout>
转换时可以在xml文件选中根布局,使用Alt + 回车
快捷键将普通布局转换为databinding布局
Activity页面初始化布局:
public class MainActivity extends Activity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//databinding为Activity设置布局文件通过DataBindingUtil
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setViewmodel(new MainViewModel());
}
}
fragment页面初始化布局:
public class MainFragment extends Fragment {
private FragmentMainBinding binding;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false);
binding.setViewmodel(new MainViewModel());
return binding.getRoot();
}
}
数据绑定时方法的优先级高于成员变量,例如:
ViewModel:
public class MainViewModwl {
public ObservableField<String> name = new ObservableField("Hello");
public String getName() {
return "World";
}
}
布局文件:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewmodel.name}"/> //TextView显示World,而不是Hello
2.Observable类型:
有如下布局:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewmodel.name}"/>
viewmodel:
//普通的String类型,name更新后不会使TextView的内容更新
public String name = "Tom";
//Observable类型,name更新后相关联的TextView内容也会自动更新
public ObservableField<String> name = new ObservableField<>("Tom");
//LiveData不能用于绑定UI
public MutableLiveData<String> name = new MutableLiveData<>();
如何实现自己的Observable类型?
查看源码发现各种Observable类型的对象,如ObservableInt,ObservableFiled等,他们都继承了BaseObservable类:
public class BaseObservable implements Observable {
private transient PropertyChangeRegistry mCallbacks;
@Override
public void addOnPropertyChangedCallback(@NonNull OnPropertyChangedCallback callback) {
synchronized (this) {
if (mCallbacks == null) {
mCallbacks = new PropertyChangeRegistry();
}
}
mCallbacks.add(callback);
}
public void notifyChange() {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.notifyCallbacks(this, 0, null);
}
}
要想实现普通数据类型绑定到UI,可以这样:
public class User extends BaseObservable { //1.继承BaseObservable
public String name;
public void setName(String name) {
this.name = name;
notifyChange(); //2.通知更新
}
}
3.ObservableField<T>和继承BaseObservable的区别:
DataModel:
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
设有如下布局:
<TextView
...
android:text="@{viewmodel.user.name}"/>
//这里的user.name虽然是private的,但还是可以通过getName方法引用到
//除了user.name,也可以通过user.getName或user.getName()获取到name
ViewModel:
public ObservableField<User> user = new ObservableField<>(new User("Google"));
这样Google字样可以显示在UI上,但是当我们想要通过修改User的属性触发UI更新:
public ObservableField<User> user = new ObservableField<>(new User("Google"));
//这种方式并不能触发UI更新
public void setUserName(String name) {
user.get().setName("Android");
}
//这种方式才可以更新UI,但如果User是一个复杂对象,new一个User就不现实了
public void setUserName(String name) {
user.set(new User("Android"))
}
因为第一种方式修改的只是user的内容,而setName前后user对象始终是同一个,所以第一种方式不能触发UI更新
要想使得第一种方式可以更新UI,就要把User修改成这样:
public class User extends BaseObservable { //1.继承BaseObservable
public String name;
public void setName(String name) {
this.name = name;
notifyChange(); //2.通知更新
}
}
这样我们就可以不用这样写ViewModel了:
public ObservableField<User> user = new ObservableField();
public ObservableField<String> userName = new ObservableField();
public ObservableInt userAge = new ObservableInt();
4.事件绑定:
<data>
<variable
name="viewmodel"
type="com.example.mtest.MainViewModel" />
<variable
name="callback"
type="android.view.View.OnClickListener" />
</data>
<TextView
...这里不需要把textView设置为clickable,就可以点击
android:onClick="@{callback}"/>
<TextView
...这里的onClick需要是OnClickListener类型
android:onClick="@{callback::onClick}"/>
<TextView
...这里viewmodel的类型没有限制
android:onClick="@{v->viewmodel.setName("Android")}"/>
5.在布局文件中调用静态方法:
public class Utils {
public static String toUpperCase(String s){
return s.toUpperCase();
}
}
布局文件:
<data>
<import type="com.example.mtest.User" />
<variable
name="viewmodel"
type="com.example.mtest.MainViewModel" />
</data>
<TextView
android:layout_width="200dp"
android:layout_height="100dp"
android:layout_marginTop="100dp"
android:text="@{Utils.toUpperCase(viewmodel.name)}" />
6.运算符:
布局文件中的某些特殊字符沿用了Html中的转义字符,如:
&& &&
"" ""
< <
> >
* ×
/ ÷
空格
??符号:
<TextView
...
android:text="@{viewmodel.firstName ?? viewmodel.lastName }" />
等价于
<TextView
...
android:text="@{viewmodel.firstName != null ? viewmodel.firstName : viewmodel.lastName }" />
7.用BindingAdapter为控件增加自定义属性:
新建一个类,类名随意,里面的方法名也随意
import android.databinding.BindingAdapter;
public class MyBindingAdapter {
@BindingAdapter("url")
public static void setUrl(TextView view, String url) {
view.setText("https://" + url);
}
//这样写可以覆盖TextView原本的属性
@BindingAdapter("android:text")
public static void setText(TextView view, String text) {
view.setText(text);
}
}
<TextView
...
url="@{viewmodel.firstName}" />
8.BindingConversion
import android.databinding.BindingConversion;
public class MyBindingAdapter {
@BindingConversion
public static String conversionString(String text) {
if (text.startsWith("www.")) {
return "https://" + text;
}
return text;
}
}
这样可以检查所有控件的String类型的输入,并且把"www."开头的字符串前面都加上”https://“
9.双向绑定:
1.系统控件双向绑定:
<EditText
android:layout_width="match_parent"
android:layout_height="100dp"
android:text="@={viewmodel.name}"/>
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="@={viewmodel.on}" />
ViewModel:
public ObservableField<String> name = new ObservableField<>("Google");
public ObservableBoolean on = new ObservableBoolean();
2.自定义控件双向绑定:
如果我们对自定义的属性使用双向绑定数据,就会产生错误,例如:
@BindingAdapter("content")
public static void setText(final Editor, String text) {
editor.setText(text);
}
<EditText
...
content="@={viewmodel.user.name}"/>
以下将实现一个自定义控件,并且实现它的自定义属性双向绑定:
public class SimpleEditor extends FrameLayout {
private EditText editor;
private Context context;
private EditorTextChangeCallback callback;
public SimpleEditor(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
private void init() {
inflate(context, R.layout.layout_simple_editor, this);
editor = (EditText) findViewById(R.id.root_view);
editor.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s != null && callback != null) {
callback.onTextChange(s.toString());
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
public void setTextChangeCallback(EditorTextChangeCallback callback) {
this.callback = callback;
}
public void setText(String text) {
this.editor.setText(text);
}
public String getText() {
return editor.getText().toString();
}
public interface EditorTextChangeCallback {
void onTextChange(String s);
}
//这个接口方法提供了SimpleEditor的自定义属性
@BindingAdapter("content")
public static void setText(final SimpleEditor editor, String text) {
//反向绑定会触发数据在View和ViewModel层之间来回传递造成死循环,所以一定要增加不相等判定
if (!text.equals(editor.getText())) {
editor.setText(text);
}
}
//这里定义了一个属性textChangeCallback(名字随意),这个字段并不是作为自定义属性供布局xml文件使用的;
//它的作用仅仅是将textChangeCallback和editor.setTextChangeCallback关联起来
//否则@InverseBindingAdapter方法不知道textChangeCallback到底对应那个接口
@BindingAdapter(value = {"onContentChange"}, requireAll = false)
public static void setTextChangeCallback(SimpleEditor editor, final InverseBindingListener textChangeListener) {
editor.setTextChangeCallback(s -> textChangeListener.onChange());
}
//需要实现双向绑定的是content属性,并且在事件textChangeCallback发生时,触发数据由控件回传给viewmodel
//这里的onContentChange事件只要保持和上一个方法中定义的事件名称相同就可以了
@InverseBindingAdapter(attribute = "content", event = "onContentChange")
public static String setInverseTest(final SimpleEditor editor) {
return editor.getText();
}
}
然后在布局文件中使用这个属性:
<com.example.mtest.SimpleEditor
...
content="@={viewmodel.name}" />
10.RecycleView中的DataBinding
参考 RecycleView