MVVM在Android中的初学之路

 

                                               MVVM在Android中的初学之路

上篇写了MVP在Android中的初学之路 https://blog.csdn.net/Ae_fring/article/details/85158579。本篇继续架构之路MVVM,记录下初学的笔记。

MVVM的模型图:

MVVM在Android中的初学之路_第1张图片

当然这里也贴上盗来的MVC和MVP的模型图:(个人感觉比较清晰)

 

MVVM在Android中的初学之路_第2张图片

 

MVVM在Android中的初学之路_第3张图片

通过图可以了解最初的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代码:

 

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。

MVVM在Android中的初学之路_第4张图片

 

MVVM在Android中的初学之路_第5张图片

MVVM在Android中的初学之路_第6张图片

MVVM在Android中的初学之路_第7张图片

由于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();
}

@Override
public 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

谢谢!

 

 

 

你可能感兴趣的:(Android架构MVP,MVC,MVVM)