最火開(kāi)源框架MVVMhabit
一. 準(zhǔn)備工作
二. 快速上手
三. 數(shù)據(jù)綁定
四. 其他
2.1回论、第一個(gè)Activity
以大家都熟悉的登錄操作為例:三個(gè)文件LoginActivty.java振诬、LoginViewModel.java椭员、activity_login.xml
2.1.1露乏、關(guān)聯(lián)ViewModel
在activity_login.xml中關(guān)聯(lián)LoginViewModel碧浊。
<layout>
<data>
<variable
type="com.goldze.mvvmhabit.ui.login.LoginViewModel"
name="viewModel"
/>
</data>
.....
</layout>
variable - type:類的全路徑
variable - name:變量名
2.1.2、繼承BaseActivity
LoginActivity繼承BaseActivity
public class LoginActivity extends BaseActivity<ActivityLoginBinding, LoginViewModel> {
//ActivityLoginBinding類是databinding框架自定生成的,對(duì)activity_login.xml
@Override
public int initContentView(Bundle savedInstanceState) {
return R.layout.activity_login;
}
@Override
public int initVariableId() {
return BR.viewModel;
}
@Override
public LoginViewModel initViewModel() {
//View持有ViewModel的引用瘟仿,如果沒(méi)有特殊業(yè)務(wù)處理辉词,這個(gè)方法可以不重寫
return ViewModelProviders.of(this).get(LoginViewModel.class);
}
}
保存activity_login.xml后databinding會(huì)生成一個(gè)ActivityLoginBinding類。(如果沒(méi)有生成猾骡,試著點(diǎn)擊Build->Clean Project)
BaseActivity是一個(gè)抽象類瑞躺,有兩個(gè)泛型參數(shù),一個(gè)是ViewDataBinding兴想,另一個(gè)是BaseViewModel幢哨,上面的ActivityLoginBinding則是繼承的ViewDataBinding作為第一個(gè)泛型約束,LoginViewModel繼承BaseViewModel作為第二個(gè)泛型約束嫂便。
重寫B(tài)aseActivity的二個(gè)抽象方法
initContentView() 返回界面layout的id
initVariableId() 返回變量的id捞镰,對(duì)應(yīng)activity_login中name="viewModel",就像一個(gè)控件的id,可以使用R.id.xxx岸售,這里的BR跟R文件一樣践樱,由系統(tǒng)生成,使用BR.xxx找到這個(gè)ViewModel的id凸丸。
選擇性重寫initViewModel()方法拷邢,返回ViewModel對(duì)象
@Override
public LoginViewModel initViewModel() {
//View持有ViewModel的引用,如果沒(méi)有特殊業(yè)務(wù)處理屎慢,這個(gè)方法可以不重寫
return ViewModelProviders.of(this).get(LoginViewModel.class);
}
注意: 不重寫initViewModel()瞭稼,默認(rèn)會(huì)創(chuàng)建LoginActivity中第二個(gè)泛型約束的LoginViewModel,如果沒(méi)有指定第二個(gè)泛型腻惠,則會(huì)創(chuàng)建BaseViewModel
2.1.3环肘、繼承BaseViewModel
LoginViewModel繼承BaseViewModel
public class LoginViewModel extends BaseViewModel {
public LoginViewModel(@NonNull Application application) {
super(application);
}
....
}
BaseViewModel與BaseActivity通過(guò)LiveData來(lái)處理常用UI邏輯,即可在ViewModel中使用父類的showDialog()集灌、startActivity()等方法悔雹。在這個(gè)LoginViewModel中就可以盡情的寫你的邏輯了!
BaseFragment的使用和BaseActivity一樣欣喧,詳情參考Demo荠商。
2.2、數(shù)據(jù)綁定
擁有databinding框架自帶的雙向綁定续誉,也有擴(kuò)展
2.2.1莱没、傳統(tǒng)綁定
綁定用戶名:
在LoginViewModel中定義
//用戶名的綁定
public ObservableField<String> userName = new ObservableField<>("");
在用戶名EditText標(biāo)簽中綁定
android:text="@={viewModel.userName}"
這樣一來(lái),輸入框中輸入了什么酷鸦,userName.get()的內(nèi)容就是什么饰躲,userName.set("")設(shè)置什么,輸入框中就顯示什么臼隔。 注意: @符號(hào)后面需要加=號(hào)才能達(dá)到雙向綁定效果嘹裂;userName需要是public的,不然viewModel無(wú)法找到它摔握。
點(diǎn)擊事件綁定:
在LoginViewModel中定義
//登錄按鈕的點(diǎn)擊事件
public View.OnClickListener loginOnClick = new View.OnClickListener() {
@Override
public void onClick(View v) {
}
};
在登錄按鈕標(biāo)簽中綁定
android:onClick="@{viewModel.loginOnClick}"
這樣一來(lái)寄狼,用戶的點(diǎn)擊事件直接被回調(diào)到ViewModel層了,更好的維護(hù)了業(yè)務(wù)邏輯
這就是強(qiáng)大的databinding框架雙向綁定的特性氨淌,不用再給控件定義id泊愧,setText(),setOnClickListener()盛正。
但是删咱,光有這些,完全滿足不了我們復(fù)雜業(yè)務(wù)的需求昂荔荨痰滋!MVVMHabit閃亮登場(chǎng):它有一套自定義的綁定規(guī)則摘能,可以滿足大部分的場(chǎng)景需求,請(qǐng)繼續(xù)往下看敲街。
2.2.2团搞、自定義綁定
還拿點(diǎn)擊事件說(shuō)吧,不用傳統(tǒng)的綁定方式多艇,使用自定義的點(diǎn)擊事件綁定逻恐。
在LoginViewModel中定義
//登錄按鈕的點(diǎn)擊事件
public BindingCommand loginOnClickCommand = new BindingCommand(new BindingAction() {
@Override
public void call() {
}
});
在activity_login中定義命名空間
xmlns:binding="http://schemas.android.com/apk/res-auto"
在登錄按鈕標(biāo)簽中綁定
binding:onClickCommand="@{viewModel.loginOnClickCommand}"
這和原本傳統(tǒng)的綁定不是一樣嗎?不墩蔓,這其實(shí)是有差別的梢莽。使用這種形式的綁定萧豆,在原本事件綁定的基礎(chǔ)之上奸披,帶有防重復(fù)點(diǎn)擊的功能,1秒內(nèi)多次點(diǎn)擊也只會(huì)執(zhí)行一次操作涮雷。如果不需要防重復(fù)點(diǎn)擊阵面,可以加入這條屬性
binding:isThrottleFirst="@{Boolean.TRUE}"
那這功能是在哪里做的呢?答案在下面的代碼中洪鸭。
//防重復(fù)點(diǎn)擊間隔(秒)
public static final int CLICK_INTERVAL = 1;
/**
* requireAll 是意思是是否需要綁定全部參數(shù), false為否
* View的onClick事件綁定
* onClickCommand 綁定的命令,
* isThrottleFirst 是否開(kāi)啟防止過(guò)快點(diǎn)擊
*/
@BindingAdapter(value = {"onClickCommand", "isThrottleFirst"}, requireAll = false)
public static void onClickCommand(View view, final BindingCommand clickCommand, final boolean isThrottleFirst) {
if (isThrottleFirst) {
RxView.clicks(view)
.subscribe(new Consumer<Object>() {
@Override
public void accept(Object object) throws Exception {
if (clickCommand != null) {
clickCommand.execute();
}
}
});
} else {
RxView.clicks(view)
.throttleFirst(CLICK_INTERVAL, TimeUnit.SECONDS)//1秒鐘內(nèi)只允許點(diǎn)擊1次
.subscribe(new Consumer<Object>() {
@Override
public void accept(Object object) throws Exception {
if (clickCommand != null) {
clickCommand.execute();
}
}
});
}
}
onClickCommand方法是自定義的样刷,使用@BindingAdapter注解來(lái)標(biāo)明這是一個(gè)綁定方法。在方法中使用了RxView來(lái)增強(qiáng)view的clicks事件览爵,.throttleFirst()限制訂閱者在指定的時(shí)間內(nèi)重復(fù)執(zhí)行置鼻,最后通過(guò)BindingCommand將事件回調(diào)出去,就好比有一種攔截器蜓竹,在點(diǎn)擊時(shí)先做一下判斷箕母,然后再把事件沿著他原有的方向傳遞。
是不是覺(jué)得有點(diǎn)意思俱济,好戲還在后頭呢嘶是!
2.2.3、自定義ImageView圖片加載
綁定圖片路徑:
在ViewModel中定義
public String imgUrl = "http://img0.imgtn.bdimg.com/it/u=2183314203,562241301&fm=26&gp=0.jpg";
在ImageView標(biāo)簽中
binding:url="@{viewModel.imgUrl}"
url是圖片路徑蛛碌,這樣綁定后聂喇,這個(gè)ImageView就會(huì)去顯示這張圖片,不限網(wǎng)絡(luò)圖片還是本地圖片蔚携。
如果需要給一個(gè)默認(rèn)加載中的圖片希太,可以加這一句
binding:placeholderRes="@{R.mipmap.ic_launcher_round}"
R文件需要在data標(biāo)簽中導(dǎo)入使用,如:
<import type="com.goldze.mvvmhabit.R" />
BindingAdapter中的實(shí)現(xiàn):創(chuàng)建自定義文件ViewAdapter.java寫入下面代碼實(shí)現(xiàn)BindingAdapter
@BindingAdapter(value = {"url", "placeholderRes"}, requireAll = false)
public static void setImageUri(ImageView imageView, String url, int placeholderRes) {
if (!TextUtils.isEmpty(url)) {
//使用Glide框架加載圖片
Glide.with(imageView.getContext())
.load(url)
.apply(new RequestOptions().placeholder(placeholderRes))
.into(imageView);
}
}
很簡(jiǎn)單就自定義了一個(gè)ImageView圖片加載的綁定酝蜒,學(xué)會(huì)這種方式跛十,可自定義擴(kuò)展。