欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JectPack组件开发2-----MVVM架构核心(DataBinding + ViewModel)

程序员文章站 2022-06-09 20:22:05
...

在之前的章节中,已经介绍过JectPack的两个组件,都与生命周期的感知有关----Lifecycle + LiveData,那么本节将会介绍跟数据绑定有关的两个组件DataBinding + ViewModel,从而引出MVVM架构的设计实现。

1、MVVM介绍

跟MVP不同的是,在MVP的P层中,主要做逻辑处理,像请求数据等;在MVVM中,是通过ViewModel代替了P层,ViewModel实现了View层和Model层的双向绑定,数据的绑定就是通过DataBinding这个组件实现的。

JectPack组件开发2-----MVVM架构核心(DataBinding + ViewModel)
首先,要想使用MVVM,那么就要你的项目支持DataBinding,在build.gradle中,添加一行代码

 dataBinding{
        enabled true
    }

2、DataBinding + ViewModel

一般来说,在使用MVVM架构的时候,更多的是在做XML布局和DataBean,一个布局对应一个页面,该页面的数据来源,通过ViewModel来提供,使用DataBinding与数据源绑定,因此在XML布局文件中,要声明该页面的数据来源。

(1)XML布局:在XML布局中,需要设置layoutdata标签,layout标签用来包裹整体的布局,data则是声明在该布局中使用到的DataBean类,如果有多个DataBean,那么就设置多个variable

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
        	//这个databean的别名
            name="user"
            //databean的全类名
            type="com.example.mvvm.User" />
    </data>

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{user.username}"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{user.password}"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</LinearLayout>

</layout>

(2)DataBean:在MVVM中,就是ViewModel层,跟之前的DataBean不同的是,这个DataBean类所要做的工作可是很多的,一方面需要监听数据是否变化,然后将数据更新在XML布局文件;另一方面,也会监听XML布局文件中数据的变化,去更新数据库数据,这就是双向绑定。

/**
 * ViewModel
 */
public class User extends BaseObservable {
    private String username;
    private String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
    @Bindable
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
        notifyPropertyChanged(BR.username);
    }
    @Bindable
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
        notifyPropertyChanged(BR.password);
    }
}

DataBean要继承BaseObservable 就是一个被观察者,XML布局会查询数据,然后在get方法需要加 @Bindable注解,获取当前的返回数据值,与XML布局中的android:text="@{user.username}"绑定;当数据源的数据发生变化时,在set方法中,加入notifyPropertyChanged(BR.user);属性值变化,BR.user就是在布局文件中设置的DataBean的别名,这样在数据发生变化时,就会同步更新到手机界面。

在完成View层和ViewModel层的基本工作完成之后,要在Activity中完成DataBinding

  super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //从Model层来
        user = new User("kobe","123456");
        mainBinding.setUser(user);

通过DataBindingUtil加载布局,会根据当前Activity的名字生成一个ActivityMainBinding 类,一般来说,我们在Model层会进行网路请求,或者数据库请求,会将数据回调到View层界面,在View层做数据调用更新UI,然后在XML布局中已经设置了DataBean的种类,所以通过ActivityMainBinding 来去设置数据,就可以将数据更新在界面。

当网路数据源发生更新之后,也会同步更新在与之绑定的布局文件上。

如果是加载一张图片到ImageView,可以通过自定义属性,来将图片加载到界面。

 //自定义属性
    @BindingAdapter("bind:header")
    public static void getImage(ImageView view,String url){
        Glide.with(view.getContext()).load(url).into(view);
    }
public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }

    public User(String username, String password,String header) {
        this.username = username;
        this.password = password;
        this.header = header;
    }

然后在布局文件中:

 <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        bind:header="@{user.header}"></ImageView>

其中内部的原理就是:在加载布局时,因为layoutdata标签,会分割为两部分,其中一部分,将这些@属性转换为tag

<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:tag="binding_1"        
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:tag="binding_2"        
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:tag="binding_3"     ></ImageView>

一些Tag的集合,然后在编译时生成的代表该Activity的DataBinding的实现类,从这些tags集合中,取出实例化,来取代findViewById

 this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.mboundView1 = (android.widget.TextView) bindings[1];
        this.mboundView1.setTag(null);
        this.mboundView2 = (android.widget.TextView) bindings[2];
        this.mboundView2.setTag(null);
        this.mboundView3 = (android.widget.ImageView) bindings[3];
        this.mboundView3.setTag(null);
        setRootTag(root);

然后,通过获取ViewModel中的数据,将数据填充到视图中。

@Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        com.example.mvvm.User user = mUser;
        java.lang.String userHeader = null;
        java.lang.String userUsername = null;
        java.lang.String userPassword = null;

        if ((dirtyFlags & 0xfL) != 0) {


            if ((dirtyFlags & 0x9L) != 0) {

                    if (user != null) {
                        // read user.header
                        userHeader = user.getHeader();
                    }
            }
            if ((dirtyFlags & 0xbL) != 0) {

                    if (user != null) {
                        // read user.username
                        userUsername = user.getUsername();
                    }
            }
            if ((dirtyFlags & 0xdL) != 0) {

                    if (user != null) {
                        // read user.password
                        userPassword = user.getPassword();
                    }
            }
        }
        // batch finished
        if ((dirtyFlags & 0xbL) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, userUsername);
        }
        if ((dirtyFlags & 0xdL) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userPassword);
        }
        if ((dirtyFlags & 0x9L) != 0) {
            // api target 1

            com.example.mvvm.User.getImage(this.mboundView3, userHeader);
        }
    }

除了上述的方式之外,在项目开发中,使用最多的,还是列表,无论是ListView,还是RecyclerView…涉及到适配器的有很多,我这里简单地用ListView介绍一下,其他的在后续项目中,如果用到,就再解释。

使用ListView时,最重要的还是数据(List数据),因此在数据源中,需要自定义属性,来得到所要展示的List数据。

public class ListBean {
    private List<User> list;

    public ListBean(List<User> list) {
        this.list = list;
    }

    public List<User> getList() {
        return list;
    }

    public void setList(List<User> list) {
        this.list = list;
    }

    @BindingAdapter("app:list")
    public static void getAdapter(ListView view,List<User> list){
        view.setAdapter(new ListViewAdapter(view.getContext(),list));
    }
}

两个参数ListView和其对应的参数,使用BindingAdapter注解,然后在布局文件中声明数据源。

 <variable
            name="rank"
            type="com.example.mvvm.ListBean" />
<ListView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:list="@{rank.list}"></ListView>

在Activity中,进行数据绑定。

 User user1 = new User();
        user1.setUsername("lili");

        User user2 = new User();
        user2.setUsername("lili");

        List<User> datas = new ArrayList<>();
        datas.add(user1);
        datas.add(user2);
        ListBean bean = new ListBean(datas);
        mainBinding.setRank(bean);

在ListView的适配器中,同样也使用DataBinding,这个使用就比较简单了。

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="user"
            type="com.example.mvvm.User" />
    </data>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_rank"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{user.username}"
        android:textSize="20dp"></TextView>

</LinearLayout>

</layout>

在适配器中,代码简直节省了不止一点!!

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
//        ViewHolder viewHolder = null;
//        if(convertView == null){
//            convertView = View.inflate(context,R.layout.item_rank,null);
//            viewHolder = new ViewHolder();
//            viewHolder.tv_rank = convertView.findViewById(R.id.tv_rank);
//            convertView.setTag(viewHolder);
//        }else{
//            viewHolder = (ViewHolder) convertView.getTag();
//        }
//        viewHolder.tv_rank.setText(datas.get(position).getUsername());
        ItemRankBinding binding = null;
        if(convertView == null) {
             binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_rank, parent, false);
        }else{
             binding = DataBindingUtil.getBinding(convertView);
        }
        binding.setUser(datas.get(position));
        
        return binding.getRoot().getRootView();
    }

我只贴出关键代码,之前使用ListView的时候,和使用的方式对比一下,代码很简洁,因为不用findViewById,所以ViewHolder也不必写了。