上篇写了MVP在Android中的初学之路 https://blog.csdn.net/Ae_fring/article/details/85158579。本篇继续架构之路MVVM,记录下初学的笔记。
MVVM的模型图:
当然这里也贴上盗来的MVC和MVP的模型图:(个人感觉比较清晰)
通过图可以了解最初的MVC的结构,由于Android中纯粹作为View的XML视图功能太弱,我们大量处理View的逻辑只能写在Activity中,这样Activity就充当了View和Controller两个角色,直接导致Activity中的代码量增多。相信大多数Android开发者都遇到过一个Acitivty数以千行的代码情况吧。
MVVM优点:
1.可重用性
可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。 布局里可以进行一个视图逻辑,并且Model发生变化,View也随着发生变化。
2.低耦合
以前Activity、Fragment中需要把数据填充到View,还要进行一些视图逻辑。现在这些都可在布局中完成(具体代码请看后面) 甚至都不需要再Activity、Fragment去findViewById。这时候Activity、Fragment只需要做好的逻辑处理就可以了。
MVVM的三大角色:
View: 对应于Activity和XML,负责View的绘制以及与用户交互。
Model: 实体模型。
ViewModel: 负责完成View与Model间的交互,负责业务逻辑。
通过图我们可以看到MVVM采用一种新的方式Data Binding来作为View和Model之间的绑定关系,增强XML视图功能 减少我们对控件的findViewbyId(下面会分析)
既然知道MVVM是一种架构模式,而DataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个工具。下面记录下代码实现部分:
app的build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.ken.mvvmdemo"
minSdkVersion 14
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
dataBinding {
enabled true
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'it.sephiroth.android.library.picasso:picasso:2.5.2.4b'
implementation 'com.squareup.okhttp3:okhttp:3.6.0'
}
最主要的 activity_main:
User类
public class User extends BaseObservable {
private String name;
private String password;
private String headimg;
public User(String name, String password, String img) {
this.name = name;
this.password = password;
this.headimg = img;
}
@BindingAdapter("bind:headimg")
public static void getHeader(ImageView view, String url) {
Picasso.with(view.getContext()).load(url).into(view);
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
notifyPropertyChanged(BR.name);
this.name = name;
}
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
notifyPropertyChanged(BR.password);
this.password = password;
}
public String getHeadimg() {
return headimg;
}
public void setHeadimg(String headimg) {
this.headimg = headimg;
}
}
可以看到根布局不在是我们以前常见的五大布局中的其中之一 而是layout 接着包裹这是DataBing的表达式写法
name="user" type="com.ken.mvvmdemo.User" /> name:引用名 type:对应需要使用到的类包名 文字显示使用@{user.name}方式: android:text="@{user.name}" 图片显示使用 app:headimg="@{user.headimg}" 后面分析源码 MainActivity代码: 现在结合MainActivity的代码分析下:首先变化的是setContentView 源码 通过源码我们了解到其实DataBind底层还是走到了之前的setContentView 不过加入了一个ViewDataBinding进来,其次返回了一个ActivityMainBinding。 由于DataBinding是编译时的工具。看到最终要执行的文件分析得出ViewDataBinding 实现了一个监听对layout的每一个控件实现Tag标识 在getBinding()后getTag()每一个控件 ActivityMainBindingImpl ViewDataBinding 最终在UI线程执行Runable----->executePendingBindings();通过源码可以看到最终让子类去实现了一个抽象的方法,所以这就解释了为什么在User类中实现加载图片为何这样写成static了。 下面记录下在ListView中使用方法 这里就写下Adapter的方法,Activity还是和最开始的写法一样,主要是适配器的getView()方法,还是以ViewDataBinding为核心 最终还是通过一系列的调用去拿到数据和前面的分析差不多的。 先记录到此附上下载源码:https://github.com/eternityzqf/MvvmDemo 谢谢!
public class MainActivity extends AppCompatActivity {
Handler handler = new Handler();
UserField userField = new UserField();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
final User user = new User("张三", "123456", "http://pic6.huitu.com/res/20130116/84481_20130116142820494200_1.jpg");
mainBinding.setUser(user);
mainBinding.setField(userField);
handler.postDelayed(new Runnable() {
@Override
public void run() {
// user.setName("李四");
// user.setPassword("123");
userField.name.set("李四");
userField.password.set("123");
}
}, 2000);
}
}
ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
/**
* Set the Activity's content view to the given layout and return the associated binding.
* The given layout resource must not be a merge layout.
*
* @param
private ActivityMainBindingImpl(android.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 3
);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView1 = (android.widget.ImageView) bindings[1];
this.mboundView1.setTag(null);
this.mboundView2 = (android.widget.TextView) bindings[2];
this.mboundView2.setTag(null);
this.mboundView3 = (android.widget.TextView) bindings[3];
this.mboundView3.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
@Override
public void invalidateAll() {
synchronized(this) {
mDirtyFlags = 0x10L;
}
requestRebind();
}
/**
* @hide
*/
protected void requestRebind() {
if (mContainingBinding != null) {
mContainingBinding.requestRebind();
} else {
synchronized (this) {
if (mPendingRebind) {
return;
}
mPendingRebind = true;
}
if (mLifecycleOwner != null) {
Lifecycle.State state = mLifecycleOwner.getLifecycle().getCurrentState();
if (!state.isAtLeast(Lifecycle.State.STARTED)) {
return; // wait until lifecycle owner is started
}
}
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
public class ComonAdapter