首先看activity_main.xml中的代码:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
<import type="android.graphics.Bitmap" />
<variable
name="resImgId"
type="int"/>
<variable
name="click"
type="org.loader.app4.MainActivity" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="300dp" android:onClick="{@click.click}"
android:orientation="vertical">
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@{resImgId}"/>
</LinearLayout>
</layout>
并且给LinearLayout设置了点击事件,和将resImgId设置到ImageView的属性android:src="@{resImgId}"。
再看看MainActivity中的代码:
public class MainActivity extends AppCompatActivity {
Custom binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this,
R.layout.activity_main);
binding.setClick(this);
}
public void click(View view) {
binding.setResImgId(R.drawable.ic_launcher);
}
}
上面的代码写好了是不能其作用的,需要
自定义属性
默认的android命名空间下,我们会发现并不是所有的属性都能直接通过data binding进行设置,比如margin,padding,还有自定义View的各种属性。
遇到这些属性,我们就需要自己去定义它们的绑定方法。
Setter
就像Data Binding会自动去查找get方法一下,在遇到属性绑定的时候,它也会去自动寻找对应的set方法。
不懂的可以参考http://blog.zhaiyifan.cn/2016/07/06/android-new-project-from-0-p8/,这篇文章的自定义属性,Setter,和BindingMethods等。
public class ImageViewAttrAdapter {
@BindingAdapter("android:src")
public static void setSrc(ImageView view, Bitmap bitmap) {
view.setImageBitmap(bitmap);
}
@BindingAdapter("android:src")
public static void setSrc(ImageView view, int resId) {
view.setImageResource(resId);
}
}
下面分析一下点击LinearLayout,图片是如何被设置到ImageView上面的。
binding.setResImgId(R.drawable.ic_launcher);
public class Custom extends android.databinding.ViewDataBinding
public void setResImgId(int resImgId) {
this.mResImgId = resImgId;
synchronized(this) {
mDirtyFlags |= 0x2L;
}
super.requestRebind();
}
这里设置了一个mDirtyFlags之后,就开始调用super.requestRebind(),看看对应的这个方法代码,这个方法在其父类ViewDataBinding类里面。
/**
* @hide
*/
protected void requestRebind() {
synchronized (this) {
if (mPendingRebind) {
return;
}
mPendingRebind = true;
}
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
重点看执行了mUIThreadHandler.post(mRebindRunnable),这就是在主线程中执行了一个Runnable了。
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
mPendingRebind = false;
}
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
// Nested so that we don't get a lint warning in IntelliJ
if (!mRoot.isAttachedToWindow()) {
// Don't execute the pending bindings until the View
// is attached again.
mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
return;
}
}
executePendingBindings();
}
};
这里也很简单,重点是executePendingBindings()。
public void executePendingBindings() {
if (mIsExecutingPendingBindings) {
requestRebind();
return;
}
if (!hasPendingBindings()) {
return;
}
mIsExecutingPendingBindings = true;
mRebindHalted = false;
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBIND, null);
// The onRebindListeners will change mPendingHalted
if (mRebindHalted) {
mRebindCallbacks.notifyCallbacks(this, HALTED, null);
}
}
if (!mRebindHalted) {
executeBindings();
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
}
}
mIsExecutingPendingBindings = false;
}
一些相关的判断,重点executeBindings()函数。
/**
* @hide
*/
protected abstract void executeBindings();
可以看到这是抽象的,也就是需要调用子类的这个方法的。
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
android.view.View.OnClickListener androidViewViewOnCli = null;
int resImgId = mResImgId;
android.graphics.Bitmap bitmap = mBitmap;
org.loader.app4.MainActivity click = mClick;
java.lang.String nameStu = null;
org.loader.app4.Student stu = mStu;
if ((dirtyFlags & 0x22L) != 0) {
// read resImgId~
resImgId = resImgId;
}
if ((dirtyFlags & 0x24L) != 0) {
// read bitmap~
bitmap = bitmap;
}
if ((dirtyFlags & 0x28L) != 0) {
// read click~
click = click;
if (click != null) {
// read android.view.View.OnClickListener~click~~click
androidViewViewOnCli = (((mAndroidViewViewOnCl == null) ? (mAndroidViewViewOnCl = new OnClickListenerImpl()) : mAndroidViewViewOnCl).setValue(click));
}
}
if ((dirtyFlags & 0x31L) != 0) {
// read stu~
stu = stu;
updateRegistration(0, stu);
if (stu != null) {
// read name~.~stu~
nameStu = stu.getName();
}
}
// batch finished
if ((dirtyFlags & 0x22L) != 0) {
// api target 1
重点: org.loader.app4.ImageViewAttrAdapter.setSrc(this.imageView, resImgId);
}
if ((dirtyFlags & 0x28L) != 0) {
// api target 1
this.mboundView1.setOnClickListener(androidViewViewOnCli);
}
if ((dirtyFlags & 0x31L) != 0) {
// api target 1
this.mboundView1.setText(nameStu);
}
if ((dirtyFlags & 0x24L) != 0) {
// api target 1
org.loader.app4.ImageViewAttrAdapter.setSrc(this.mboundView2, bitmap);
}
}
org.loader.app4.ImageViewAttrAdapter.setSrc(this.imageView, resImgId);
可以看到最终调用了静态方法ImageViewAttrAdapter.setSrc(this.imageView, resImgId)方法了。
没有纠结细节部分,只是简单的分析整个调用过程。
总结:(1)android:src="@{resImgId}",会根据resImgId生成binding的setResImgId(int resId)方法。
(2)@BindingAdapter({"android:src"}),最终生成了相关的代码中,binding的setResImgId(int resId)会调用这个静态setSrc(ImageView view, int resId)方法
(分析过程如上)。
@BindingAdapter({"android:src"})
public static void setSrc(ImageView view, int resId) {
view.setImageResource(resId);
}