众所周知, 使用dataBinding是为了配合app的mvvm架构,建立数据与ui的绑定关系, 数据更新驱动ui变化; 建立双向绑定,ui变化也能改变数据
一.Databinding的简单使用
步骤一: 创建要更新的数据
class AccountInfo : BaseObservable() {
var account: String? = null
@Bindable
get() {
return field
}
set(value) {
field = value
notifyPropertyChanged(BR.account)
}
}
注意点:在编译时如果遇到找不到@Bindable或者BR,在app下的build.gradle 引入 'kotlin-kapt',同步完后build当前项目
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
kapt {
generateStubs = true
}
步骤二:创建布局文件, 关联数据
创建完成后, 需要引入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="accountInfo"
type="com.wislie.jetpack_kotlin_demo.AccountInfo" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- @{accountInfo.account}设置数据与视图的单向绑定关系-->
<TextView
android:id="@+id/tv_account"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#1fbbee"
android:textSize="28sp"
android:text="@{accountInfo.account}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
注意点: option+Enter键 生成layout布局
步骤三: 创建ActivityXXBinding, 通过ActivityXXBinding建立数据与视图的绑定关系
创建ActivityXXBinding, 同时创建数据, 将此数据赋值给binding
class MMVMTestActivity:AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMvvmTestBinding>(
this,R.layout.activity_mvvm_test)
val accountInfo = AccountInfo()
binding.accountInfo = accountInfo
binding.lifecycleOwner = this
val accountArr = arrayListOf("1200","1300","1400")
var index = 0
thread {
while (true){
//每隔3秒,将数据更新到ui上
accountInfo.account = accountArr[index++%accountArr.size]
Thread.sleep(3000)
}
}
}
}
实现的效果:每隔3秒,ui上依次更新为1200, 1300,1400
二.DataBinding的实现原理
DataBinding采用的是注解处理器技术,编译之后会将上述的布局文件拆分成两个部分, 一个是app能识别的布局文件,前者布局文件中指明了id的视图控件, 会在另一个xml生成对应的tag; Databinding管理布局时会通过tag查找到控件视图, 当数据发生改变,会将数据设置到对应的视图控件中
2.1 layout标签的布局文件,apt生成2个文件
文件一: 页面显示的布局文件activity_mvvm_test.xml,在incremental->mergeDebugResources->stripped.dir->layout目录下
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent" android:tag="layout/activity_mvvm_test_0" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- @{accountInfo.account}建立数据与视图的单向绑定关系-->
<TextView
android:id="@+id/tv_account"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#1fbbee"
android:textSize="28sp"
android:tag="binding_1" />
</androidx.constraintlayout.widget.ConstraintLayout>
文件二: activity_mvvm_test-layout.xml,该文件在data_binding _layout_info_type_merge->debug->out目录下,原来的标签和标签, 控件中关联绑定的变量都在此文件中
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="jetpack_kotlin_demo/src/main/res/layout/activity_mvvm_test.xml"
isBindingData="true" isMerge="false"
layout="activity_mvvm_test" modulePackage="com.wislie.jetpack_kotlin_demo"
rootNodeType="androidx.constraintlayout.widget.ConstraintLayout">
<Targets>
<Target tag="layout/activity_mvvm_test_0"
view="androidx.constraintlayout.widget.ConstraintLayout">
<Expressions />
</Target>
<Target id="@+id/tv_account" tag="binding_1" view="TextView">
<Expressions>
<Expression attribute="android:text" text="accountInfo.account">
</Expression>
</Expressions>
</Target>
</Targets>
<Variables name="accountInfo" declared="true" type="com.wislie.jetpack_kotlin_demo.AccountInfo">
</Variables>
</Layout>
注意点:只有声明了id的控件,才会有tag, 布局根目录的控件tag是以"layout_"开头,普通控件的tag是以"binding下划线"开头
2.2 DataBindingUtil.setContentView
追踪进去可以看到设置会将布局设置到节点content中
activity.setContentView(layoutId);
接下来会将声明id的控件赋值给bindings数组
Object[] bindings = new Object[numBindings];
mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
省略一些非关键代码, 如果view是ViewGroup, 会查找view的子控件,并赋值给bindings[index]
private static void mapBindings(DataBindingComponent bindingComponent, View view,
Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
boolean isRoot) {
///////////////////////////// 省略/////////////////////////////
Object objTag = view.getTag();
final String tag = (objTag instanceof String) ? (String) objTag : null;
boolean isBound = false;
///////////////////////////// 省略/////////////////////////////
if (isRoot && tag != null && tag.startsWith("layout")) {
final int underscoreIndex = tag.lastIndexOf('_');
///////////////////////////// 省略/////////////////////////////
final int index = parseTagInt(tag, underscoreIndex + 1);
if (bindings[index] == null) {
bindings[index] = view;
}
isBound = true;
///////////////////////////// 省略/////////////////////////////
} else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
if (bindings[tagIndex] == null) {
bindings[tagIndex] = view;
}
isBound = true;
///////////////////////////// 省略/////////////////////////////
}
if (!isBound) {
final int id = view.getId();
if (id > 0) {
int index;
if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
bindings[index] == null) {
bindings[index] = view;
}
}
}
if (view instanceof ViewGroup) {
final ViewGroup viewGroup = (ViewGroup) view;
final int count = viewGroup.getChildCount();
int minInclude = 0;
for (int i = 0; i < count; i++) {
final View child = viewGroup.getChildAt(i);
///////////////////////////// 省略/////////////////////////////
mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
}
}
}
bindings数组填充完成后,将绑定的数据更新到对应的ui上 invalidateAll->requestRebind->mFrameCallback.run()->executePendingBindings->executeBindingsInternal->executeBindings
executeBindings如下, 也就是textView.setText()
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String accountInfoAccount = null;
com.wislie.jetpack_kotlin_demo.AccountInfo accountInfo = mAccountInfo;
if ((dirtyFlags & 0x7L) != 0) {
if (accountInfo != null) {
// read accountInfo.account
accountInfoAccount = accountInfo.getAccount();
}
}
// batch finished
if ((dirtyFlags & 0x7L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvAccount, accountInfoAccount);
}
}
2.3: 实时更新ui
accountInfo.account = accountArr[index++%accountArr.size]
赋值之后本质是调用了account的set函数
set(value) {
field = value
notifyPropertyChanged(BR.account)
}
-> BaseObservable PropertyChangeRegistry.notifyCallbacks(this, fieldId, null);
->....
经过层层调用, 会到ViewDataBinding的requestRebind, 回到了和步骤二将默认数据填充到ui上一样的流程了
注意点:绑定的成员变量可以使用Observable包装类,LiveData等
binding.lifecycleOwner = this
这一行的作用和之前LiveData中LifecycleBoundObserver的activeStateChanged 一样的意思, 当宿主处于活跃状态,即可见状态时, 可以进行更新UI, 源码ViewDataBinding的requestRebind方法如下
protected void requestRebind() {
if (mContainingBinding != null) {
mContainingBinding.requestRebind();
} else {
final LifecycleOwner owner = this.mLifecycleOwner;
if (owner != null) {
Lifecycle.State state = owner.getLifecycle().getCurrentState();
if (!state.isAtLeast(Lifecycle.State.STARTED)) {
return; // wait until lifecycle owner is started
}
}
synchronized (this) {
if (mPendingRebind) {
return;
}
mPendingRebind = true;
}
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
requestRebind在什么时候调用, 设置默认值和绑定数据发生改变时.
目前有几个疑问:
1.如果绑定数据发生改变了, 调用requestRebind前突然锁屏了,解锁屏幕后,这样是否会造成数据的丢失?
2.activity.setContentView(layoutId) 此layoutId是哪个,是我们手写的布局,还是拆分的布局; 按道理应该是拆分的布局,是如何定位到的?