转载请标明出处https://www.jianshu.com/p/fb8d33168f57
前言:毕业快3年了,出来工作之后一直是倾向于 Android相关平台逆向以及广告SDK的开发和学习,一直想补一下Android开发相关的知识点,国庆前群里老哥们聊起了MVVM和DataBinding,我心里终究是按耐不住好奇心了,于是乎也打开了浏览器,学了起来,其实本来一开始是想学MVVM先,毕竟databinding也是配合MVVM的一套工具,一种实现。但是机缘巧合先学起了这个,后面再补一篇MVVM的学习。
1.DataBinding有什么用,能解决什么问题
DataBinding是一个Google官方发布的框架,是为了应对日常开发中数据层和UI层之间的传递的。我们日常开发中需要自己手动去写findViewById,setOnClickListener这类厚重的代码。这个框架就可以简便的实现数据和UI的传递,UI对数据的监听,UI的刷新。
2.DataBinding的使用
DataBinding作为官方直推,更是不用添加依赖使用,而是gradle直接支持了,我们只需要在项目app下build.gradle之中的android下添加下面代码就可以使用,如果添加了之后无法使用,可以尝试升级AS版本。
android {
dataBinding {
enabled = true;
}
}
3.DataBinding的代码实现
3.1.视图与数据变量的绑定
<layout xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="text"
type="com.llyyyw.databinding.TextBean" />
<variable
name="activity"
type="com.llyyyw.databinding.MainActivity" />
</data>
<!--往下开始常规的UI布局-->
</layout>
DataBinding对XML布局格式有一定的要求,需要在xml最外层使用layout包裹,之后里面分别写入data以及正常UI布局。其中data标签中声明你变量名以及变量类,我这里就是声明了自己写的TextBean类,命名为text。供下面使用。
3.2.视图数据的使用以及控件事件监听
DataBinding会根据activity和xml自动生成一个ViewDataBinding类,这个类负责了view的管理和逻辑,事件监听以及变量的管理。我这个类是放在build文件夹下,这个看看它是如何实现的。
ViewDataBinding.jpg
之后在想要操作view的类中进行viewDataBinding的实例化,设置数据。之后XML可以通过@{}的格式配合变量名获取,比方说@{text.title}。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
textBean = new TextBean();
textBean.setTitle("dataBinding你好");
textBean.setText("dataBinding你好");
viewDataBinding.setActivity(this);
viewDataBinding.setText(textBean);
}
<TextView
android:id="@+id/tv_title2"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{text.title}" />
这个text就是传递过去的“dataBinding你好”。
除了数据的传递,还有对于view事件的监听,比如点击事件。
<TextView
android:id="@+id/tv_title2"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{activity::updateTitle}"
android:text="@{text.title}" />
这里就是调用了传递进去的activity中的updateTitle方法,这里activity::updateTitle这种格式的调用,规定这个方法必须要是一个有View参数的方法。
public void updateTitle(View view) {
textBean.setTitle("dataBinding你好" + i);
i++;
}
当然这个activity完全可以是你想要调用的任意类,updateTitle也可以是类里的任意方法。只要你在data里绑定了数据。
除此之外,还有其他比如@{() -> activity.updateTitle(obj)},这种写法无论带不带参数,方法参数对应上即可。所以其实也可以实现在xml中传递参数到方法里。
3.3.BindingAdapter,视图控件对于数据变化监听。
BindingAdapter是DataBinding提供的一个用于控件对于变量发生变化的监听的。
@BindingAdapter("android:text_title_check")
public static void checkTitle(TextView view, CharSequence text) {
CharSequence oldText = view.getText();
if (!haveContentsChanged(text, oldText)) {
return; // 数据没有变化不进行刷新视图
}
view.setText(text);
}
这里@BindingAdapter()中可以根据你自己的需要进行命名,方法名也可以随意,但是要注意参数以及XML中调用时的对应。
<TextView
android:id="@+id/tv_title2"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text_title_check="@{text.title}"/>
这样子,当传入BindingAdapter标注的方法的变量发生变化时,就会调用该方法,该方法可以进行一定的逻辑操作,比如图片的加载,文本的判定。
3.4.单向和双向数据绑定
前面我们使用了单向数据绑定,数据变化引起视图Textview中的更新。
DataBinding提供了BaseObservable、ObservableField、ObservableCollection三种方式实现。
我这里使用BaseObservable为例。
public class TextBean extends BaseObservable implements Serializable {
private String title;
private String text;
@Bindable
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
notifyPropertyChanged(BR.title);
}
@Bindable
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
// 需要手动刷新
notifyPropertyChanged(BR.text);
}
}
Bindable注解get方法,notifyPropertyChanged在set方法刷新。也可以使用ObservableField这个类,对BaseObservable绑定和解绑的进一步封装。
BaseObservable是官方实现的观察器,该接口自己实现了绑定和解绑,我们只需要专注于数据的传递更新。
这样子配合@{}就可以达到数据变化,视图也实时变化。
但是我现在有一个需求,我现在有一个输入框editText,或者一个多选框,这个框的结果我必须知道并拿来使用,正常的开发中,我们会通过获取控件之后getText之类的方法获取框中的实时内容,很麻烦。但是DataBinding提供了我们双向绑定。我们只需要通过@={}的写法,就可以实现
视图改变==>数据实时改变。
数据改变==>视图实时改变。
<EditText
android:id="@+id/et_title"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={text.title}" />
效果如下:
demo.gif
像这样,点击textview的事件中对数据进行修改,由于textview单向绑定,editText双向绑定,都指向同一个数据,所以都实时的改变。editText修改的时候也同理。
3.5.DataBinding自提供变量
如果现在我们有一个需求,需要在布局中使用上下文的变量,那我们该怎么做。正常思路就是继续使用data,Variable标签。
其实DataBinding自己也提供了一些Variable的,比方说context。 是可以直接使用的. 代表View的getContext()。
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{()->activity.packageName(context.applicationContext.packageName)}"
android:text="@{context.applicationContext.packageName}"/>
我这里就配合上面讲的控件事件,将context.applicationContext.packageName作为一个string参数,传递给了packageName方法。
public void packageName(String packageName) {
Toast.makeText(this, packageName, Toast.LENGTH_SHORT).show();
}
contextToast.jpg
3.6.DataBinding和include的使用
关于include的使用,其实主要是主activity和包含布局之间要使用 bind来传递数据。包含布局中也要用data和variable实现数据变量和view的绑定。注意bind:xxx和variable name="xxx",这里两者xxx必须是一样的才能匹配到。
<include
layout="@layout/include_main"
bind:textText="@{text.text}"/>
<layout>
<data>
<variable
name="textText"
type="String" />
</data>
<TextView
tools:context=".MainActivity"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{textText}">
</TextView>
</layout>
4.DataBinding的简单分析
DataBinding的简单使用可以说是很简单,省去了很多代码,简化了数据和视图之间的交互。不禁我们就想知道,他其实是怎么实现的,我也很好奇,然后点开了源码,看的云里雾里,但是大概是,DataBinding对自己定义的结构的xml进行了解析,配合activity创建ViewDataBinding,获取各种view以及监听数据的过程。
ActivityMainBinding对象的生成
由DataBinding对象获取的方法DataBindingUtil.setContentView最终追溯到这个方法。
public class DataBindingUtil {
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}
}
这个方法最终返回的其实就是相对应ActivityMainBindingImpl的对象。
ActivityMainBindingImpl.jpg
ViewDataBinding实例化:
protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
mBindingComponent = bindingComponent;
mLocalFieldObservers = new WeakListener[localFieldCount];
this.mRoot = root;
if (Looper.myLooper() == null) {
throw new IllegalStateException("DataBinding must be created in view's UI Thread");
}
if (USE_CHOREOGRAPHER) {
mChoreographer = Choreographer.getInstance();
mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mRebindRunnable.run();
}
};
} else {
mFrameCallback = null;
mUIThreadHandler = new Handler(Looper.myLooper());
}
}
并且ViewDataBinding会通过view创建对应的WeakListener,弱引用类型接口作为观察者,利于被回收,同时很好的处理内存泄露的情况。之后则是update时的逻辑。
@Override
public boolean setVariable(int variableId, @Nullable Object variable) {
boolean variableSet = true;
if (BR.activity == variableId) {
setActivity((com.llyyyw.databinding.MainActivity) variable);
}
else if (BR.text == variableId) {
setText((com.llyyyw.databinding.TextBean) variable);
}
else {
variableSet = false;
}
return variableSet;
}
public void setText(@Nullable com.llyyyw.databinding.TextBean Text) {
updateRegistration(0, Text);
this.mText = Text;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.text);
super.requestRebind();
}
/**
* @hide
*/
protected boolean updateRegistration(int localFieldId, Observable observable) {
return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
/**
* @hide
*/
protected boolean updateRegistration(int localFieldId, ObservableList observable) {
return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
}
/**
* @hide
*/
protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
}
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
}
};
private static final CreateWeakListener CREATE_LIST_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return new WeakListListener(viewDataBinding, localFieldId).getListener();
}
};
private static final CreateWeakListener CREATE_MAP_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return new WeakMapListener(viewDataBinding, localFieldId).getListener();
}
};
最终流程是:
源代码全部不好贴出来,口述一下。相对应ActivityMainBindingImpl的实例化,数据绑定在视图UI线程中创建,以及调用父类ViewDataBinding的executePendingBindings直到executeBindings这个抽象方法,指向的是ActivityMainBindingImpl的executeBindings方法,注册WeakListener监听。
当我们设置数据,BindingImpl的setVariable调用控件set方法,调用updateRegistration监听。当BaseObservable变化,触发notifyPropertyChanged,触发回调,也就是对应的WeakListener。就会更新视图。
这里我们讲完了数据变化从而view变化的原理,那view变化引起数据变化呢。
首先要知道ViewDataBinding是如何具体保存view的。前面说明了ActivityMainBindingImpl才是整个DataBinding的核心实现类,也调用了它的executeBindings方法,这个方法里其实就是对控件的一系列操作,控件的设值,控件的各种监听事件。
if ((dirtyFlags & 0x15L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etTitle, textTitle);
com.llyyyw.databinding.dbAdapter.TextBindingAdapter.checkTitle(this.tvTitle2, textTitle);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvTitle2, textTitle);
}
if ((dirtyFlags & 0x10L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etTitle, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etTitleandroidTextAttrChanged);
this.tvTitle.setOnClickListener(mCallback1);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvTitle, contextApplicationContextPackageName);
}
if ((dirtyFlags & 0x19L) != 0) {
// api target 1
this.mboundView01.setTextText(textText);
}
if ((dirtyFlags & 0x12L) != 0) {
// api target 1
this.tvTitle2.setOnClickListener(activityUpdateTitleAndroidViewViewOnClickListener);
}
private androidx.databinding.InverseBindingListener etTitleandroidTextAttrChanged = new androidx.databinding.InverseBindingListener() {
@Override
public void onChange() {
// Inverse of text.title
// is text.setTitle((java.lang.String) callbackArg_0)
java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(etTitle);
// localize variables for thread safety
// text.title
java.lang.String textTitle = null;
// text
com.llyyyw.databinding.TextBean text = mText;
// text != null
boolean textJavaLangObjectNull = false;
textJavaLangObjectNull = (text) != (null);
if (textJavaLangObjectNull) {
text.setTitle(((java.lang.String) (callbackArg_0)));
}
}
};
只看其中的EditText控件可以发现,DataBinding是这样通过etTitleandroidTextAttrChanged这个监听器去实时获取控件的值进而对数据进行实时更新的。
5.总结
DataBinding的使用并不难,当然DataBinding还有许多其他的使用。深入去了解DataBinding可以发现里面的大奥秘,我这里云里雾里也只能理解这么冰山一角。学习使我快乐!
网友评论