最火开源框架MVVMhabit
一. 准备工作
二. 快速上手
三. 数据绑定
四. 其他
2.1、第一个Activity
以大家都熟悉的登录操作为例:三个文件LoginActivty.java、LoginViewModel.java、activity_login.xml
2.1.1、关联ViewModel
在activity_login.xml中关联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框架自定生成的,对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的引用,如果没有特殊业务处理,这个方法可以不重写
return ViewModelProviders.of(this).get(LoginViewModel.class);
}
}
保存activity_login.xml后databinding会生成一个ActivityLoginBinding类。(如果没有生成,试着点击Build->Clean Project)
BaseActivity是一个抽象类,有两个泛型参数,一个是ViewDataBinding,另一个是BaseViewModel,上面的ActivityLoginBinding则是继承的ViewDataBinding作为第一个泛型约束,LoginViewModel继承BaseViewModel作为第二个泛型约束。
重写BaseActivity的二个抽象方法
initContentView() 返回界面layout的id
initVariableId() 返回变量的id,对应activity_login中name="viewModel",就像一个控件的id,可以使用R.id.xxx,这里的BR跟R文件一样,由系统生成,使用BR.xxx找到这个ViewModel的id。
选择性重写initViewModel()方法,返回ViewModel对象
@Override
public LoginViewModel initViewModel() {
//View持有ViewModel的引用,如果没有特殊业务处理,这个方法可以不重写
return ViewModelProviders.of(this).get(LoginViewModel.class);
}
注意: 不重写initViewModel(),默认会创建LoginActivity中第二个泛型约束的LoginViewModel,如果没有指定第二个泛型,则会创建BaseViewModel
2.1.3、继承BaseViewModel
LoginViewModel继承BaseViewModel
public class LoginViewModel extends BaseViewModel {
public LoginViewModel(@NonNull Application application) {
super(application);
}
....
}
BaseViewModel与BaseActivity通过LiveData来处理常用UI逻辑,即可在ViewModel中使用父类的showDialog()、startActivity()等方法。在这个LoginViewModel中就可以尽情的写你的逻辑了!
BaseFragment的使用和BaseActivity一样,详情参考Demo。
2.2、数据绑定
拥有databinding框架自带的双向绑定,也有扩展
2.2.1、传统绑定
绑定用户名:
在LoginViewModel中定义
//用户名的绑定
public ObservableField<String> userName = new ObservableField<>("");
在用户名EditText标签中绑定
android:text="@={viewModel.userName}"
这样一来,输入框中输入了什么,userName.get()的内容就是什么,userName.set("")设置什么,输入框中就显示什么。 注意: @符号后面需要加=号才能达到双向绑定效果;userName需要是public的,不然viewModel无法找到它。
点击事件绑定:
在LoginViewModel中定义
//登录按钮的点击事件
public View.OnClickListener loginOnClick = new View.OnClickListener() {
@Override
public void onClick(View v) {
}
};
在登录按钮标签中绑定
android:onClick="@{viewModel.loginOnClick}"
这样一来,用户的点击事件直接被回调到ViewModel层了,更好的维护了业务逻辑
这就是强大的databinding框架双向绑定的特性,不用再给控件定义id,setText(),setOnClickListener()。
但是,光有这些,完全满足不了我们复杂业务的需求啊!MVVMHabit闪亮登场:它有一套自定义的绑定规则,可以满足大部分的场景需求,请继续往下看。
2.2.2、自定义绑定
还拿点击事件说吧,不用传统的绑定方式,使用自定义的点击事件绑定。
在LoginViewModel中定义
//登录按钮的点击事件
public BindingCommand loginOnClickCommand = new BindingCommand(new BindingAction() {
@Override
public void call() {
}
});
在activity_login中定义命名空间
xmlns:binding="http://schemas.android.com/apk/res-auto"
在登录按钮标签中绑定
binding:onClickCommand="@{viewModel.loginOnClickCommand}"
这和原本传统的绑定不是一样吗?不,这其实是有差别的。使用这种形式的绑定,在原本事件绑定的基础之上,带有防重复点击的功能,1秒内多次点击也只会执行一次操作。如果不需要防重复点击,可以加入这条属性
binding:isThrottleFirst="@{Boolean.TRUE}"
那这功能是在哪里做的呢?答案在下面的代码中。
//防重复点击间隔(秒)
public static final int CLICK_INTERVAL = 1;
/**
* requireAll 是意思是是否需要绑定全部参数, false为否
* View的onClick事件绑定
* onClickCommand 绑定的命令,
* isThrottleFirst 是否开启防止过快点击
*/
@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秒钟内只允许点击1次
.subscribe(new Consumer<Object>() {
@Override
public void accept(Object object) throws Exception {
if (clickCommand != null) {
clickCommand.execute();
}
}
});
}
}
onClickCommand方法是自定义的,使用@BindingAdapter注解来标明这是一个绑定方法。在方法中使用了RxView来增强view的clicks事件,.throttleFirst()限制订阅者在指定的时间内重复执行,最后通过BindingCommand将事件回调出去,就好比有一种拦截器,在点击时先做一下判断,然后再把事件沿着他原有的方向传递。
是不是觉得有点意思,好戏还在后头呢!
2.2.3、自定义ImageView图片加载
绑定图片路径:
在ViewModel中定义
public String imgUrl = "http://img0.imgtn.bdimg.com/it/u=2183314203,562241301&fm=26&gp=0.jpg";
在ImageView标签中
binding:url="@{viewModel.imgUrl}"
url是图片路径,这样绑定后,这个ImageView就会去显示这张图片,不限网络图片还是本地图片。
如果需要给一个默认加载中的图片,可以加这一句
binding:placeholderRes="@{R.mipmap.ic_launcher_round}"
R文件需要在data标签中导入使用,如:
<import type="com.goldze.mvvmhabit.R" />
BindingAdapter中的实现:创建自定义文件ViewAdapter.java写入下面代码实现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);
}
}
很简单就自定义了一个ImageView图片加载的绑定,学会这种方式,可自定义扩展。