有關(guān)Databinding與MVVM的一些事

DataBinding

說(shuō)到DataBinding,大家就會(huì)想到雙向綁定蛹稍。那究竟什么是雙向綁定,其實(shí)對(duì)于剛接觸的人來(lái)說(shuō)是需要去理解一下的长赞。

MVVM中,ViewModel是互相隔離的姆另。假設(shè)有以下的EditText布局:

android:text="@{echo.text}"

那么顯而易見(jiàn),當(dāng)echo.text發(fā)生變化時(shí),我們希望EditText中的android:text屬性可以自動(dòng)變化忙芒。這是Model層->View層的綁定凰棉。

反之,當(dāng)用戶(hù)通過(guò)View層在EditText中進(jìn)行輸入時(shí),我們希望echo.text字段也可以同步更新到最新的輸入值膀哲。這是ViewModel層的綁定往产。這一點(diǎn)很多人都會(huì)忽略被碗。

那么首先我們看是Model層->View層的綁定是怎么實(shí)現(xiàn)的,在DataBinding中大家都知道有以下兩種方式:

  • Model繼承BaseObservable,get帶上@Bindable注解
  • 相應(yīng)字段使用Observablexxx變量,如下text字段(其實(shí)Observablexxx就是繼承BaseObservable)

可以簡(jiǎn)單看下BaseObservable的源碼,后面會(huì)用到:

public class BaseObservable implements Observable {
   @Override
    public void addOnPropertyChangedCallback(@NonNullOnPropertyChangedCallback callback) {
        ...
        mCallbacks.add(callback);
    }
   
    public void notifyPropertyChanged(int fieldId) {
        ...
        mCallbacks.notifyCallbacks(this, fieldId, null);
    }

就是一個(gè)回調(diào)模式。

public class Echo {
  public ObservableField<String> text = new ObservableField<>();
}

當(dāng)使用ObservableField后,就真正使用了觀察者的模式仿村。也就是說(shuō)當(dāng)調(diào)用setEcho方法后,一個(gè)監(jiān)聽(tīng)器就被注冊(cè)了,這個(gè)監(jiān)聽(tīng)器會(huì)在每次text字段被更新后去更新視圖蛮放。

先來(lái)一段小小的源碼分析,每個(gè)layout生成的xxxBinding都是關(guān)鍵的類(lèi),里面有一個(gè)executeBinding方法。

我們來(lái)看,一個(gè)簡(jiǎn)單的

  • android:text="@{model.str}"

會(huì)生成什么模板代碼奠宜?

   @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String modelStr = null;
        me.lizz0y.myapplication.VM model = mModel;

        if ((dirtyFlags & 0x3L) != 0) {
                if (model != null) {
                    // read model.str
                    modelStr = model.str;
                }
        }
        // batch finished
        if ((dirtyFlags & 0x3L) != 0) {
            // api target 1
            android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, modelStr);
        }
    }

關(guān)鍵代碼:

modelStr = model.str;
android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, modelStr);//這個(gè)`xxxAdapter`后面再說(shuō),這里就看出簡(jiǎn)單的賦值就好了

如果變成

  • public ObservableField<String> str = new ObservableField<String>("sss");

我們會(huì)發(fā)現(xiàn)包颁,在executeBinding中多了一句:

updateRegistration(0, modelStr);//localFieldId

將這個(gè)變量的fieldIdmodel.str這個(gè)Observable綁定在一起,同時(shí)使用前面的addOnPropertyChangedCallbackViewBinding類(lèi)作為回調(diào)傳進(jìn)去。

最終,當(dāng)我們對(duì)mode.str進(jìn)行set操作時(shí),一系列回調(diào)最終走到ViewDataBinding

   @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
            case 0 :
                return onChangeModelStr((android.databinding.ObservableField<java.lang.String>) object, fieldId);
        }
        return false;
    }

其實(shí)就是對(duì)str這個(gè)變量賦予臟位,讓下次屏幕刷新時(shí)更新這個(gè)變量對(duì)應(yīng)的View压真。

介紹一些常用的運(yùn)算符:

運(yùn)算符

@BindingConversion

如果在xml里我這么寫(xiě):android:background="@{@color/blue}"

會(huì)報(bào)錯(cuò),因?yàn)?code>background應(yīng)該是drawable娩嚼。所以要進(jìn)行自動(dòng)轉(zhuǎn)化,所以需要進(jìn)行如下定義:

//轉(zhuǎn)化@color/blue為drawable
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

經(jīng)過(guò)前面分析也很簡(jiǎn)單:

android.databinding.adapters.ViewBindingAdapter.setBackground(
this.mboundView0,me.lizz0y.myapplication.VM.convertColorToDrawable(
    mboundView0.getResources().getColor(R.color.blue)));
        

當(dāng)然這里顯然有人會(huì)問(wèn),假設(shè)我定義了多個(gè)怎么破,結(jié)論就是后面的覆蓋前面的。滴肿。岳悟。

@BindAdapter

舉個(gè)栗子就明白了:

@BindingAdapter({"imageUrl"})  
public static void loadImage(ImageView view, String u) {  
    RequestOptions options = new RequestOptions()  
            .centerCrop()  
            .placeholder(R.mipmap.ic_launcher_round)  
            .error(R.mipmap.ic_launcher)  
            .priority(Priority.HIGH)  
            .diskCacheStrategy(DiskCacheStrategy.NONE);  

    Glide.with(view.getContext()).applyDefaultRequestOptions(options).load(u).transition(new DrawableTransitionOptions().crossFade(1000)).into(view);  
}  

xml里這么寫(xiě):

 <ImageView  
    android:layout_width="100dp"  
    android:layout_height="100dp"  
    app:imageUrl="@{user.url}" />  

這就每次更新user.url時(shí)就會(huì)自動(dòng)重新設(shè)置圖片。

DataBindingset屬性attr時(shí)會(huì)先看View有沒(méi)有setXXX方法泼差。如果沒(méi)有就去找有沒(méi)有BindingAdapter注解設(shè)置對(duì)應(yīng)的方法贵少。

前面分析源碼的時(shí)候提到過(guò)

if ((dirtyFlags & 0x3L) != 0) {
    // api target 1
    android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, modelStr);
}

我們看這里的BindingAdapter

@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
    final CharSequence oldText = view.getText();
    if (text == oldText || (text == null && oldText.length() == 0)) {
        return;
    }
    if (text instanceof Spanned) {
        if (text.equals(oldText)) {
            return; // No change in the spans, so don't set anything.
        }
    } else if (!haveContentsChanged(text, oldText)) {
        return; // No content changes, so don't set anything.
    }
    view.setText(text);
}

可以看到會(huì)比較oldText&newText,以防無(wú)限循環(huán)。

Component

直接看這篇吧,寫(xiě)的很好

我們可以定義多個(gè)BindingAdapter,但究竟想要哪個(gè)發(fā)揮作用呢堆缘? 就可以使用這個(gè)Component

BindingMethod

該注解可以幫助我們重新命名view屬性對(duì)應(yīng)的setter方法名稱(chēng)滔灶。

@BindingMethods({@BindingMethod(type = NestedScrollView.class, attribute = "custom", method = "setMyCustomAttr")})

注解在類(lèi)上。

個(gè)人覺(jué)得他與BindingAdapter的區(qū)別在于:

  • 參數(shù),BindingAdapter可以拿到view引用
  • component

監(jiān)聽(tīng)屬性變更

databinding讓我們省去了各種監(jiān)聽(tīng)函數(shù),但有的時(shí)候我們需要在屬性變化時(shí)做一些額外的事情:

mModel.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
    @Override
    public void onPropertyChanged(Observable observable, int i) {
        if (i == BR.name) {
            Toast.makeText(TwoWayActivity.this, "name changed",
                    Toast.LENGTH_SHORT).show();
        } else if (i == BR.password) {
            Toast.makeText(TwoWayActivity.this, "password changed",
                    Toast.LENGTH_SHORT).show();
        }
    }
});

雙重綁定

下面我們來(lái)說(shuō)說(shuō)如果從View->Model的綁定吼肥。我們看在前面已有的基礎(chǔ)上,我們?nèi)绾巫约簩?shí)現(xiàn)雙重綁定

<EditText
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:hint="Text 1"
  android:text="@{echo.text}"
  android:addTextChangedListener="@{echo.watcher}"/>

假設(shè)有兩個(gè)EditText都使用了android:text,可以看到一個(gè)改變并不能連帶帶動(dòng)另一個(gè)EditText,因?yàn)?code>EditText的輸入并沒(méi)有手動(dòng)去調(diào)用setField方法录平。所以顯而易見(jiàn)需要再使用DataBinding賦予textChangedListener

 public TextWatcher watcher = new TextWatcherAdapter() {
    @Override public void afterTextChanged(Editable s) {
      if (!Objects.equals(text.get(), s.toString())) { //防止無(wú)限循環(huán)
        text.set(s.toString());
      }
    }
  };
customBinding
public class BindableString extends BaseObservable {
  private String value;
  public String get() {
    return value != null ? value : “”;
  }
  public void set(String value) {
    if (!Objects.equals(this.value, value)) {
      this.value = value;
      notifyChange();
    }
  }
  public boolean isEmpty() {
    return value == null || value.isEmpty();
  }
}

@BindingConversion
public static String convertBindableToString(
    BindableString bindableString) {
  return bindableString.get();
}

當(dāng)主動(dòng)調(diào)用BindableString.set時(shí)會(huì)通過(guò)notifyChange去觸發(fā)UI更新,UI更新時(shí)調(diào)用convertBindableToString取出string綁定

或者BindAdapter:

@BindingAdapter({“app:binding”})
public static void bindEditText(EditText view,
    final BindableString bindableString) {
  Pair<BindableString, TextWatcherAdapter> pair = 
    (Pair) view.getTag(R.id.bound_observable);
  if (pair == null || pair.first != bindableString) {
    if (pair != null) {
     view.removeTextChangedListener(pair.second);
    }
    TextWatcherAdapter watcher = new TextWatcherAdapter() {
      public void onTextChanged(CharSequence s, 
          int start, int before, int count) {
        bindableString.set(s.toString());
      }
    };
    view.setTag(R.id.bound_observable, 
       new Pair<>(bindableString, watcher));
    view.addTextChangedListener(watcher);
  }  
  String newValue = bindableString.get();
  if (!view.getText().toString().equals(newValue)) {
    view.setText(newValue);
  }
}
<EditText
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:hint="Text 1"
  app:binding="@{echo.text}"/>

或者可以使用BindAdapter:

@BindingAdapter({“app:binding”})
public static void bindEditText(EditText view,
    final BindableString bindableString) {
  Pair<BindableString, TextWatcherAdapter> pair = 
    (Pair) view.getTag(R.id.bound_observable);
  if (pair == null || pair.first != bindableString) {
    if (pair != null) {
     view.removeTextChangedListener(pair.second);
    }
    TextWatcherAdapter watcher = new TextWatcherAdapter() {
      public void onTextChanged(CharSequence s, 
          int start, int before, int count) {
        bindableString.set(s.toString());
      }
    };
    view.setTag(R.id.bound_observable, 
       new Pair<>(bindableString, watcher));
    view.addTextChangedListener(watcher);
  }  
  String newValue = bindableString.get();
  if (!view.getText().toString().equals(newValue)) {
    view.setText(newValue);
  }
}

當(dāng)然,google不可能真的這么蠢。缀皱。讓我們自己去實(shí)現(xiàn)這一套,它在xml里給我們提供了很簡(jiǎn)單的運(yùn)算符@=

@=

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inputType="textNoSuggestions"
    android:text="@={model.name}"/>

這么搞完當(dāng)EditText更新時(shí)就自動(dòng)更新model.name字段斗这。當(dāng)然,肯定很好奇背后的實(shí)現(xiàn)(這一part看了我半天。啤斗。) 它的實(shí)現(xiàn)是由多個(gè)注解完成的,先看其中兩個(gè):

  • @InverseBindingAdapter
  • @InverseBindingListener

TextViewBindingAdapter.java:

@BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",
            "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)
public static void setTextWatcher(TextView view, final BeforeTextChanged before,
        final OnTextChanged on, final AfterTextChanged after,
        final InverseBindingListener textAttrChanged) {
    final TextWatcher newValue;
    if (before == null && after == null && on == null && textAttrChanged == null) {
        newValue = null;
    } else {
        newValue = new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                if (before != null) {
                    before.beforeTextChanged(s, start, count, after);
                }
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (on != null) {
                    on.onTextChanged(s, start, before, count);
                }
                if (textAttrChanged != null) {
                    textAttrChanged.onChange();
                }
            }

            @Override
            public void afterTextChanged(Editable s) {
                if (after != null) {
                    after.afterTextChanged(s);
                }
            }
        };
    }
    final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
    if (oldValue != null) {
        view.removeTextChangedListener(oldValue);
    }
    if (newValue != null) {
        view.addTextChangedListener(newValue);
    }
}
    
@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
public static String getTextString(TextView view) {
    return view.getText().toString();
}

xxxViewBinding.java

  private android.databinding.InverseBindingListener mboundView2androidTextAttrChanged = new android.databinding.InverseBindingListener() {
        @Override
        public void onChange() {
            // Inverse of model.str.get()
            //         is model.str.set((java.lang.String) callbackArg_0)
            java.lang.String callbackArg_0 = android.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView2);
            // localize variables for thread safety
            // model
            me.lizz0y.myapplication.VM model = mModel;
            // model.str != null
            boolean modelStrJavaLangObjectNull = false;
            // model != null
            boolean modelJavaLangObjectNull = false;
            // model.str.get()
            java.lang.String modelStrGet = null;
            // model.str
            android.databinding.ObservableField<java.lang.String> modelStr = null;
            modelJavaLangObjectNull = (model) != (null);
            if (modelJavaLangObjectNull) {
                modelStr = model.str;
                modelStrJavaLangObjectNull = (modelStr) != (null);
                if (modelStrJavaLangObjectNull) {
                    modelStr.set(((java.lang.String) (callbackArg_0)));
                }
            }
        }
    };

也很簡(jiǎn)單,當(dāng)text發(fā)生變化時(shí),觸發(fā)onTextChange,然后調(diào)用mboundView2androidTextAttrChanged.onChange,里面調(diào)用了由@InverseBindingAdapter注解的getTextString去獲取值賦給model

總結(jié)一下,假設(shè)你要給一個(gè)自定義屬性雙向綁定,寫(xiě)上@=時(shí):你需要寫(xiě)以下函數(shù):

@InverseBindingAdapter(attribute = "refreshing", event = "refreshingAttrChanged")
public static boolean getRefreshing(PhilView view) { //賦值時(shí)來(lái)這里取
    return isRefreshing;
}

@BindingAdapter(value = {"refreshingAttrChanged"}, requireAll = false)
public static void setRefreshingAttrChanged(PhilView view, final InverseBindingListener inverseBindingListener) {
    Log.d(TAG, "setRefreshingAttrChanged");

    if (inverseBindingListener == null) {
        view.setRefreshingListener(null);
    } else {
        mInverseBindingListener = inverseBindingListener;
        view.setRefreshingListener(mOnRefreshingListener);
    }
}
@InverseMethod & @InverseBindingMethod[s]

參考這篇

只是簡(jiǎn)化了一下@InverseBindingAdapter的注解表箭。

與RecyclerView

public class MyViewHolder extends RecyclerView.ViewHolder {
    private final ItemBinding binding;

    public MyViewHolder(ItemBinding binding) {
        super(binding.getRoot());
        this.binding = binding;
    }

    public void bind(Item item) {
        binding.setItem(item);
        binding.executePendingBindings();
    }
}

強(qiáng)刷executePendingBinding

強(qiáng)制綁定操作馬上執(zhí)行,而不是推遲到下一幀刷新時(shí)钮莲。RecyclerView 會(huì)在 onBindViewHolder 之后立即測(cè)量 View免钻。如果因?yàn)榻壎ㄍ七t到下一幀繪制時(shí)導(dǎo)致錯(cuò)誤的數(shù)據(jù)被綁定到 View 中, View 會(huì)被不正確地測(cè)量,因此這個(gè) executePendingBindings() 方法非常重要臂痕!

todoApp結(jié)構(gòu)

1.png

現(xiàn)在先讓我們忘記前面說(shuō)的一切有關(guān)雙向綁定的事情伯襟。猿涨。握童。來(lái)看看google推出的架構(gòu)LiveData&ViewModel

我們先看平時(shí)開(kāi)發(fā)時(shí)會(huì)有哪些問(wèn)題:

通常Android系統(tǒng)來(lái)管理UI controllers(如Activity、Fragment)的生命周期叛赚,由系統(tǒng)響應(yīng)用戶(hù)交互或者重建組件澡绩,用戶(hù)無(wú)法操控稽揭。當(dāng)組件被銷(xiāo)毀并重建后,原來(lái)組件相關(guān)的數(shù)據(jù)也會(huì)丟失肥卡,如果數(shù)據(jù)類(lèi)型比較簡(jiǎn)單溪掀,同時(shí)數(shù)據(jù)量也不大,可以通過(guò)onSaveInstanceState()存儲(chǔ)數(shù)據(jù)步鉴,組件重建之后通過(guò)onCreate()揪胃,從中讀取Bundle恢復(fù)數(shù)據(jù)。但如果是大量數(shù)據(jù)氛琢,不方便序列化及反序列化喊递,則上述方法將不適用。

UI controllers經(jīng)常會(huì)發(fā)送很多異步請(qǐng)求阳似,有可能會(huì)出現(xiàn)UI組件已銷(xiāo)毀骚勘,而請(qǐng)求還未返回的情況,因此UI controllers需要做額外的工作以防止內(nèi)存泄露撮奏。
當(dāng)Activity因?yàn)榕渲米兓N(xiāo)毀重建時(shí)俏讹,一般數(shù)據(jù)會(huì)重新請(qǐng)求,其實(shí)這是一種浪費(fèi)畜吊,最好就是能夠保留上次的數(shù)據(jù)泽疆。

解決fragmentfragment之間通信的問(wèn)題

LiveData

LiveData,顧名思義和生命周期綁定,解決上面的第二個(gè)異步問(wèn)題:

  • 能夠感知組件(Fragment、Activity玲献、Service)的生命周期于微;

  • 只有在組件出于激活狀態(tài)(STARTED、RESUMED)才會(huì)通知觀察者有數(shù)據(jù)更新青自;

public class NameViewModel extends ViewModel{
    // Create a LiveData with a String
    private MutableLiveData<String> mCurrentName;
    // Create a LiveData with a String list
    private MutableLiveData<List<String>> mNameListData;

    public MutableLiveData<String> getCurrentName() {
        if (mCurrentName == null) {
            mCurrentName = new MutableLiveData<>();
        }
        return mCurrentName;
    }

    public MutableLiveData<List<String>> getNameList(){
        if (mNameListData == null) {
            mNameListData = new MutableLiveData<>();
        }
        return mNameListData;
    }
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mNameViewModel = ViewModelProviders.of(this).get(NameViewModel.class);
    mNameViewModel.getCurrentName().observe(this,(String name) -> {
        mTvName.setText(name);
        Log.d(TAG, "currentName: " + name);
    }); // 訂閱LiveData中當(dāng)前Name數(shù)據(jù)變化株依,以lambda形式定義Observer
    mNameViewModel.getNameList().observe(this, (List<String> nameList) -> {
        for (String item : nameList) {
            Log.d(TAG, "name: " + item);
        }
    }); // 訂閱LiveData中Name列表數(shù)據(jù)變化,以lambda形式定義Observer
}

當(dāng)組件處于激活狀態(tài)延窜,并且mCurrentName變量發(fā)生變化時(shí),fragment觀察者就會(huì)收到監(jiān)聽(tīng)

ViewModel

[站外圖片上傳中...(image-93bfcd-1526989876647)]

說(shuō)實(shí)話一開(kāi)始看到這個(gè)我總以為跟MVVMViewModel有什么異曲同工之妙,事實(shí)證明我想多了恋腕。此ViewModel是用來(lái)存儲(chǔ)和管理UI相關(guān)的數(shù)據(jù)。

ViewModel是生存在整個(gè)生命周期內(nèi)的,所以在這個(gè)類(lèi)中不能存在android.content.Context; 簡(jiǎn)單來(lái)說(shuō)不能有viewcontext的引用,所以一般都會(huì)傳ApplicationContext進(jìn)去逆瑞。

用法很簡(jiǎn)單:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

很容易看出它為什么可以解決以上問(wèn)題, ViewModelProviders.of(this).get(MyViewModel.class);調(diào)用這個(gè)時(shí),內(nèi)部代碼會(huì)幫我們做存儲(chǔ)荠藤。至于怎么做存儲(chǔ),也很常見(jiàn),加了一個(gè)fragmentsetRetain(true)就可以了。這樣重建恢復(fù)后拿的還是同一個(gè)ViewModel,因此顯然數(shù)據(jù)也都還在获高。同時(shí),一個(gè)Activity對(duì)應(yīng)的兩個(gè)fragemt也可以通信:

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

使用getActivity拿到同一份ViewModel,就可以拿到同一份數(shù)據(jù)哈肖。也很容易看到,其實(shí)LiveData是需要與ViewModel結(jié)合在一起用的

todoApp-mvvm-live

todoApp可以看出最明顯的區(qū)別:

@NonNull
public static TaskDetailViewModel obtainViewModel(FragmentActivity activity) {
    // Use a Factory to inject dependencies into the ViewModel
    ViewModelFactory factory = ViewModelFactory.getInstance(activity.getApplication());

    return ViewModelProviders.of(activity, factory).get(TaskDetailViewModel.class);
}

其次,其實(shí)這個(gè)例子還是跟dataBinding搞在一起了,否則拿回?cái)?shù)據(jù)更新UI時(shí)需要用到大量LiveDataobserve函數(shù)。

最后所以用了LiveData的作用在哪,我們看一個(gè)例子,點(diǎn)擊某個(gè)按鈕后跳轉(zhuǎn)Activity:

before


public class TasksActivity extends AppCompatActivity implements TaskItemNavigator, TasksNavigator {

    ....
    
}


@Nullable
private WeakReference<TaskItemNavigator> mNavigator;


ViewModel中:

public void taskClicked() {
    String taskId = getTaskId();
    if (taskId == null) {
        // Click happened before task was loaded, no-op.
        return;
    }
    if (mNavigator != null && mNavigator.get() != null) { //煩躁
        mNavigator.get().openTaskDetails(taskId);
    }
}

after

TasksActivity.java

// Subscribe to "open task" event
mViewModel.getOpenTaskEvent().observe(this, new Observer<String>() {
    @Override
    public void onChanged(@Nullable String taskId) {
        if (taskId != null) {
            openTaskDetails(taskId);
        }
    }
});

// mTasksViewModel.getOpenTaskEvent().setValue(task.getId());

利用liveData的生命周期特性,就不用管activity是否已經(jīng)消失念秧。

調(diào)試

Databinding想調(diào)試自動(dòng)生成的代碼,需要在setting里選擇Reference code generated by the compiler

一些額外的技巧

image

image

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末淤井,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌币狠,老刑警劉巖游两,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異漩绵,居然都是意外死亡贱案,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)止吐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)宝踪,“玉大人,你說(shuō)我怎么就攤上這事碍扔‰饶” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵蕴忆,是天一觀的道長(zhǎng)颤芬。 經(jīng)常有香客問(wèn)我,道長(zhǎng)套鹅,這世上最難降的妖魔是什么站蝠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮卓鹿,結(jié)果婚禮上菱魔,老公的妹妹穿的比我還像新娘。我一直安慰自己吟孙,他們只是感情好澜倦,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著杰妓,像睡著了一般藻治。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上巷挥,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天桩卵,我揣著相機(jī)與錄音,去河邊找鬼倍宾。 笑死雏节,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的高职。 我是一名探鬼主播钩乍,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼怔锌!你這毒婦竟也來(lái)了寥粹?” 一聲冷哼從身側(cè)響起变过,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎排作,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體亚情,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妄痪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了楞件。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衫生。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖土浸,靈堂內(nèi)的尸體忽然破棺而出罪针,到底是詐尸還是另有隱情,我是刑警寧澤黄伊,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布泪酱,位于F島的核電站,受9級(jí)特大地震影響还最,放射性物質(zhì)發(fā)生泄漏墓阀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一拓轻、第九天 我趴在偏房一處隱蔽的房頂上張望斯撮。 院中可真熱鬧,春花似錦扶叉、人聲如沸勿锅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)溢十。三九已至,卻和暖如春达吞,著一層夾襖步出監(jiān)牢的瞬間茶宵,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工宗挥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乌庶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓契耿,卻偏偏與公主長(zhǎng)得像瞒大,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子搪桂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容