近期,在筆者開源的BindingListAdapter庫中出現了這樣的一個Issue。java
這實際上是一個在列表中比較常見的問題,在沒有找到比較好的解決辦法以前,確實都是經過整項刷新notifyDataChanged
來保證數據顯示的正確性。到後來的notifyItemChanged
和更佳的DiffUtil
,說明開發者們一直都在想辦法來解決並優化它。android
但其實若是你使用DataBinding,作這個局部刷新或者說是定點刷新,就很簡單了,這多是大多數使用DataBinding的開發者並不知道的技巧。git
能夠先看看實際的效果github
關鍵點就在於不要直接綁定具體的值到xml中,應先使用ObservableField
包裹一層。編程
class ItemViewModel constructor( val data:String){
//not
//val count = data
//val liked = false
//should
val count = ObservableField<String>(data)
val liked = ObservableBoolean()
}
//partial_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="item"
type="io.ditclear.app.partial.PartialItemViewModel" />
<variable
name="presenter"
type="io.ditclear.bindingadapterx.ItemClickPresenter" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:onClick="@{(v)->presenter.onItemClick(v,item)}"
>
<TextView
android:text="@{item.count}" />
<ImageView
android:src="@{item.liked?@drawable/ic_action_liked:@drawable/ic_action_unlike}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
複製代碼
當UI須要改變時,改變ItemViewModel的數據便可。數組
//activity
override fun onItemClick(v: View, item: PartialItemViewModel) {
item.toggle()
}
// ItemViewModle
fun toggle(){
liked.set(!liked.get())
count.set("$data ${if (liked.get()) "liked" else ""}")
}
複製代碼
至於爲何?這就要說到DataBinding更新UI的原理了。app
當咱們在項目中運用DataBinding,並將xml文件轉換爲DataBinding形式以後。通過編譯build,會生成相應的binding文件,好比partial_list_item.xml
就會生成對應的PartialListItemBinding
文件,這是一個抽象類,還會有一個PartialListItemBindingImpl
實現類實現具體的渲染UI的方法executeBindings
。ide
當數據有改變的時候,就會從新調用executeBindings
方法,更新UI,那怎麼作到的呢?oop
咱們先來看PartialListItemBinding
的構造方法.post
public abstract class PartialListItemBinding extends ViewDataBinding {
@NonNull
public final ImageView imageView;
@Bindable
protected PartialItemViewModel mItem;
@Bindable
protected ItemClickPresenter mPresenter;
protected PartialListItemBinding(DataBindingComponent _bindingComponent, View _root, int _localFieldCount, ImageView imageView) {
//調用父類的構造方法
super(_bindingComponent, _root, _localFieldCount);
this.imageView = imageView;
}
//...
}
複製代碼
調用了父類ViewDataBinding
的構造方法,並傳入了三個參數,這裏看第三個參數_localFieldCount
,它表明xml中存在幾個ObservableField
形式的數據,繼續追蹤.
protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
this.mBindingComponent = bindingComponent;
//考點1
this.mLocalFieldObservers = new ViewDataBinding.WeakListener[localFieldCount];
this.mRoot = root;
if (Looper.myLooper() == null) {
throw new IllegalStateException("DataBinding must be created in view's UI Thread");
} else {
if (USE_CHOREOGRAPHER) {
//考點2
this.mChoreographer = Choreographer.getInstance();
this.mFrameCallback = new FrameCallback() {
public void doFrame(long frameTimeNanos) {
ViewDataBinding.this.mRebindRunnable.run();
}
};
} else {
this.mFrameCallback = null;
this.mUIThreadHandler = new Handler(Looper.myLooper());
}
}
}
複製代碼
經過《觀察》,發現其根據localFieldCount
初始化了一個WeakListener
數組,名爲mLocalFieldObservers
。另外一個重點是初始化了一個mFrameCallback
,在回調中執行了mRebindRunnable.run
。
當生成的PartialListItemBindingImpl
對象調用executeBindings
方法時,經過updateRegistration
會對mLocalFieldObservers
數組中的內容進行賦值。
隨之生成的是相應的WeakPropertyListener
,來看看它的定義。
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback implements ObservableReference<Observable> {
final WeakListener<Observable> mListener;
public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
mListener = new WeakListener<Observable>(binder, localFieldId, this);
}
//...
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
ViewDataBinding binder = mListener.getBinder();
if (binder == null) {
return;
}
Observable obj = mListener.getTarget();
if (obj != sender) {
return; // notification from the wrong object?
}
//劃重點
binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
}
複製代碼
當ObservableField
的值有改變的時候,onPropertyChanged
會被調用,而後就會回調binder
(即binding對象)的handleFieldChange
方法,繼續觀察。
private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
if (!this.mInLiveDataRegisterObserver) {
boolean result = this.onFieldChange(mLocalFieldId, object, fieldId);
if (result) {
this.requestRebind();
}
}
}
複製代碼
若是值有改變 ,result爲true,接着requestRebind
方法被執行。
protected void requestRebind() {
if (this.mContainingBinding != null) {
this.mContainingBinding.requestRebind();
} else {
synchronized(this) {
if (this.mPendingRebind) {
return;
}
this.mPendingRebind = true;
}
if (this.mLifecycleOwner != null) {
State state = this.mLifecycleOwner.getLifecycle().getCurrentState();
if (!state.isAtLeast(State.STARTED)) {
return;
}
}
//劃重點
if (USE_CHOREOGRAPHER) { // SDK_INT >= 16
this.mChoreographer.postFrameCallback(this.mFrameCallback);
} else {
this.mUIThreadHandler.post(this.mRebindRunnable);
}
}
}
複製代碼
在上述代碼最後,能夠看到sdk版本16以上會執行
this.mChoreographer.postFrameCallback(this.mFrameCallback);
,16如下則是經過Handler
。
關於postFrameCallBack,給的註釋是
Posts a frame callback to run on the next frame.
,簡單理解就是發生在下一幀即16ms以後的回調。關於
Choreographer
,推薦閱讀 Choreographer 解析
但無論如何,最終都是調用mRebindRunnable.run
,來看看對它的定義。
/** * Runnable executed on animation heartbeat to rebind the dirty Views. */
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
mPendingRebind = false;
}
processReferenceQueue();
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
方法,到這裏,DataBinding更新UI的邏輯咱們也就所有打通了。
筆者已經使用了DataBinding好幾年的時間,深切的體會到了它對於開發效率的提高,決不下於Kotlin,用好了它就是劍客最鋒利的寶劍,削鐵如泥,用很差便自損八百。爲此,上一年我專門寫了一篇DataBinding實用指南講了講本身的使用經驗,同時開源了運用DataBinding來簡化RecyclerView適配器的BindingListAdapter,但願更多的開發者喜歡它。
GitHub示例:github.com/ditclear/Bi…
==================== 分割線 ======================
若是你想了解更多關於MVVM、Flutter、響應式編程方面的知識,歡迎關注我。
你能夠在如下地方找到我:
簡書:www.jianshu.com/u/117f1cf0c…
Github: github.com/ditclear