MVVM在Android中的初学之路

上篇写了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:

<?xml version="1.0" encoding="utf-8"?>                                                

 

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代码:

 

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);    }}

现在结合MainActivity的代码分析下:首先变化的是setContentView

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  Type of the generated binding class. * @param activity The Activity whose content View should change. * @param layoutId The resource ID of the layout to be inflated, bound, and set as the *                 Activity's content. * @return The binding associated with the inflated content view or {@code null} if the * layoutId is not a data binding layout. */// @Nullable don't annotate with Nullable. It is unlikely to be null and makes using it from// kotlin really ugly. We cannot make it NonNull w/o breaking backward compatibility.public static  T setContentView(@NonNull Activity activity,        int layoutId) {    return setContentView(activity, layoutId, sDefaultComponent);}/** * 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  Type of the generated binding class. * @param bindingComponent The DataBindingComponent to use in data binding. * @param activity The Activity whose content View should change. * @param layoutId The resource ID of the layout to be inflated, bound, and set as the *                 Activity's content. * @return The binding associated with the inflated content view or {@code null} if the * layoutId is not a data binding layout. */// @Nullable don't annotate with Nullable. It is unlikely to be null and makes using it from// kotlin really ugly. We cannot make it NonNull w/o breaking backward compatibility.public static  T setContentView(@NonNull Activity activity,        int layoutId, @Nullable DataBindingComponent bindingComponent) {    activity.setContentView(layoutId);    View decorView = activity.getWindow().getDecorView();    ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);    return bindToAddedViews(bindingComponent, contentView, 0, layoutId);}

通过源码我们了解到其实DataBind底层还是走到了之前的setContentView 不过加入了一个ViewDataBinding进来,其次返回了一个ActivityMainBinding。

 

由于DataBinding是编译时的工具。看到最终要执行的文件分析得出ViewDataBinding 实现了一个监听对layout的每一个控件实现Tag标识 在getBinding()后getTag()每一个控件

ActivityMainBindingImpl

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();}@Overridepublic void invalidateAll() {    synchronized(this) {            mDirtyFlags = 0x10L;    }    requestRebind();}

 

ViewDataBinding

/** * @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);        }    }}

最终在UI线程执行Runable----->executePendingBindings();通过源码可以看到最终让子类去实现了一个抽象的方法,所以这就解释了为什么在User类中实现加载图片为何这样写成static了。

 

下面记录下在ListView中使用方法

public class ComonAdapter extends BaseAdapter {    private Context context;    private LayoutInflater inflater;    private int layoutId;    private int variableId;    private List list;    public ComonAdapter(Context context, int layoutId, int variableId, List list) {        this.context = context;        this.inflater = LayoutInflater.from(context);        this.layoutId = layoutId;        this.variableId = variableId;        this.list = list;    }    @Override    public int getCount() {        return list.size();    }    @Override    public Object getItem(int position) {        return list.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewDataBinding viewDataBinding;        if (convertView == null) {            viewDataBinding = DataBindingUtil.inflate(inflater, layoutId, parent, false);        } else {            viewDataBinding = DataBindingUtil.getBinding(convertView);        }        //数据部分        viewDataBinding.setVariable(variableId, list.get(position));        return viewDataBinding.getRoot().getRootView();    }}

这里就写下Adapter的方法,Activity还是和最开始的写法一样,主要是适配器的getView()方法,还是以ViewDataBinding为核心 最终还是通过一系列的调用去拿到数据和前面的分析差不多的。

先记录到此附上下载源码:https://github.com/eternityzqf/MvvmDemo

谢谢!

 

 

 

更多相关文章

  1. 【Android开发】基本组件-图像视图
  2. 第十四周实验报告:实验四 Android程序设计
  3. Android帧动画在应用启动时同步启动
  4. Android定制ListView的界面(使用继承自ArrayAdapter的自定义适配
  5. Android(安卓)中的ORM框架
  6. Android(安卓)锁屏功能
  7. 操作 Android(安卓)模拟器
  8. Android(安卓)处理屏幕旋转
  9. Android(安卓)AIDL实现调用第三方登录

随机推荐

  1. android new feature on 4.2
  2. android 颜色(color)
  3. Install ADB And Fastboot Android(安卓)
  4. Android文件合并时,打包出错
  5. 【Android深入解析】Manifest配置文件解
  6. android 自定义水平的ProgressBar
  7. android 程序崩溃后重启
  8. Android(安卓)Dialog在底部显示且宽度mat
  9. 菜鸟对使用AIDL的一点理解
  10. android Contacts应用中不容易理解的点