MVVM在Android中的初学之路

 

                                               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"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="user"
            type="com.ken.mvvmdemo.User" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:contentDescription="@null"
            app:headimg="@{user.headimg}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="@{user.name}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="@{user.password}" />
    </LinearLayout>

</layout>

 

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 接着<data>包裹这是DataBing的表达式写法

<variable

    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 <T> 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 extends ViewDataBinding> 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 <T> 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 extends ViewDataBinding> 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();
}

@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<T> extends BaseAdapter {

    private Context context;
    private LayoutInflater inflater;
    private int layoutId;
    private int variableId;
    private List<T> list;

    public ComonAdapter(Context context, int layoutId, int variableId, List<T> 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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
初学者在学习Android开发时,可能会遇到MVVM框架。MVVM框架是一种用于构建Android应用程序的架构模式,它将视图(View)、模型(Model)和视图模型(ViewModel)分离。\[1\]在传统的Android开发,业务逻辑和UI处理通常都在Activity,而MVVM框架通过将业务逻辑和UI分离,使得代码更加清晰和可维护。\[1\]MVVM框架已经存在一段时间了,并且在许多项目得到了广泛应用。\[2\]对于初学者来说,理解和使用MVVM框架可能有一定的难度,但是通过学习简单易懂的MVVM框架,并结合Jetpack组件的使用,可以帮助初学者更好地理解和应用MVVM框架。\[2\] #### 引用[.reference_title] - *1* *2* [Android MVVM框架搭建(一)ViewModel + LiveData + DataBinding](https://blog.csdn.net/qq_38436214/article/details/120820238)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [android初学者_android开发的初学者细节](https://blog.csdn.net/weixin_26723981/article/details/108649624)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逆流的剑客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值