MVVM RecyclerView ItemBinding----从此不写适配器

一个月没有写博客了;

一个月时间里将以前的两个个项目MVVM Databinding搭配MVC结构改成了MVVM结构;

使用ViewModel处理业务逻辑,Activity只负责UI的处理;

使用组件化方式将两个项目合并;

本篇文章不详细说明组件化,关于组件化文章很多,具体问题再写一篇记录组件化;

过程中遇到很多的问题,其中要说的就是从以前RecyclerView搭配Adapter说起;

我这个项目界面不多,但是列表特别多,列表嵌套列表更是多得要命;

什么滑动冲突,什么item展示不完整等问题在以前开发的时候遇到次数特别多;

以前还是每个列表一个Adapter;

这次最终目标就是使用ItemDatabind的方式将Adapter省略掉,但是还是需要一个ViewModel来处理一些东西;


在MVVM使用RecyclerView的时候,找资料找demo找了挺长的时间;

下面贴出一个github上很棒的开源项目---binding-collection-adapter

地址:点我点我点我

英文比较好的还有扩展性强的童鞋可以直接去看他的项目说明;

注:github阅读代码觉得累的可以看下我另一篇博客,给chorme安装个插件:

github代码阅读的chrome插件(insightio-for-github)

让你在github看代码更轻松,目录更清晰,看个效果图:



好了,懒得去的可以看我下面的栗子演示,并且附带一些我遇到的问题和解决思路(到这里应该能剩点人吧,恩希望吧,要不我下面的都白写了);


我建个空项目,然后什么MVVM初始化那一套先弄上,假设现在可以放心使用databinding了;

gradle中引入刚才那个开源库:

// recyclerView的Databinding套装
    compile 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter:2.2.0'
    compile 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-recyclerview:2.2.0'

然后建个DemoActvity:

public class DemoActivity extends AppCompatActivity {

    private InputaActivityDemoBinding demoBinding;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        demoBinding = DataBindingUtil.setContentView(DemoActivity.this, R.layout.inputa_activity_demo);
        
        demoBinding.setDemoViewModel(new DemoViewModel(this));
    }
}
可以看出,初始化activity没啥东西,就是将ViewModel初始化设置到xml中;

下面看看activity的xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:binding="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="demoViewModel"
            type="com.reliable.inputa.ui.demo.DemoViewModel"/>

        <import type="me.tatarka.bindingcollectionadapter2.LayoutManagers"/>
    </data>

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

        <android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            binding:items="@{demoViewModel.viewModelObservableList}"
            binding:layoutManager="@{LayoutManagers.linear()}"
            binding:itemBinding="@{demoViewModel.itemBinding}"/>

    </LinearLayout>

</layout>
可以看到,只有一个recyclerView;
上面有三个以前没见过的属性,其中LayoutaManager是开源项目中带的,直接import进xml使用即可,如果你想设置一行多个条目,或者其他的展现形式,可以点进去看他的源码,它使用工厂模式,创建不同manager;

现在假如你想设置每行四列的列表,那么上面的linear你可以换成 grid(4)

具体还有哪些command+左键进去瞅瞅,很简单;

剩下俩个属性在ViewModel中设置,关键点来了:

看下DemoViewModel:

import android.content.Context;
import android.databinding.ObservableArrayList;
import android.databinding.ObservableList;
import com.reliable.baselib.base.BaseViewModel;
import com.reliable.inputa.BR;
import com.reliable.inputa.R;
import java.util.ArrayList;
import java.util.List;
import me.tatarka.bindingcollectionadapter2.ItemBinding;

/**
 * 列表Activity的ViewModel
 * Created by ge on 2018/2/5.
 */
public class DemoViewModel extends BaseViewModel{

    // 给RecyclerView添加ObservableList
    public ObservableList<DemoItemViewModel> viewModelObservableList = new ObservableArrayList<>();
    // 给RecyclerView添加ItemView
    public ItemBinding<DemoItemViewModel> itemBinding
            = ItemBinding.of(BR.demoItemViewModel, R.layout.inputa_demo_item);
    

    DemoViewModel(Context context){
        super(context);


        // 造点假数据,这里你可以是网络请求或者数据库获取
        List<DemoEntity> entityList = new ArrayList<>();
        DemoEntity entity = new DemoEntity();
        entity.setAddress("辽宁大连高新园区腾讯大厦");
        entity.setName("葛大宝");
        DemoEntity entity1 = new DemoEntity();
        entity1.setAddress("西安市碑林区中科创星");
        entity1.setName("葛大宝1");
        entityList.add(entity);
        entityList.add(entity1);
        // -----------------华丽分割线---------------

        // 初始化item条目
        int itemCount = entityList.size();
        for (int i = 0;i < itemCount;i++){
            viewModelObservableList.add(new DemoItemViewModel(context));
        }
    }
}

关键就是:

// 给RecyclerView添加ObservableList
    public ObservableList<DemoItemViewModel> viewModelObservableList = new ObservableArrayList<>();
    // 给RecyclerView添加ItemView
    public ItemBinding<DemoItemViewModel> itemBinding
            = ItemBinding.of(BR.demoItemViewModel, R.layout.inputa_demo_item);
初始化的时候添加observableList,即循环创建条目,并将每个条目的数据对象传过去:

// 初始化item条目
        int itemCount = entityList.size();
        for (int i = 0;i < itemCount;i++){
            viewModelObservableList.add(new DemoItemViewModel(context, entityList.get(i)));
        }
其中itemViewModel我起名叫DemoItemVIewModel:

import android.content.Context;
import android.databinding.ObservableField;
import com.reliable.baselib.base.BaseViewModel;

/**
 * item条目ViewModel
 * Created by ge on 2018/2/5.
 */
public class DemoItemViewModel extends BaseViewModel{

    public ObservableField<DemoEntity> observableField = new ObservableField<>();

    DemoItemViewModel(Context context, DemoEntity demoEntity){
        super(context);

        observableField.set(demoEntity);
    }

}
注:其中ObservableField是设置个观察者数据,后面当我们数据发生变化时,绑定在xml的展示的效果自己会刷新;

下面看看item的xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="demoItemViewModel"
            type="com.reliable.inputa.ui.demo.DemoItemViewModel"/>

    </data>

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

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{demoItemViewModel.observableField.get().getName()}"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{demoItemViewModel.observableField.get().getAddress()}"/>

    </LinearLayout>

</layout>
好了,完事了,看着其实挺多的,刚开始写我也有点懵逼,慢慢理解它的思路,理解理解发现这样的结构很棒,比我写adapter舒服多了。

到这里一个itemBinding的用法就介绍完了;


我们实际的项目会比这个复杂的多,例如:

 列表有头部,有尾部,每个item根据type不一样布局不一样,item的点击事件应该在哪里写?等等


下面看看如果你的列表头部尾部或者多item样式的布局应该怎么写;

很简单,只需要给刚才observableArrayList那里修改一下:

// 给RecyclerView添加ObservableList
    public ObservableList<DemoItemViewModel> viewModelObservableList = new ObservableArrayList<>();
    public MergeObservableList<Object> headFooterItems = new MergeObservableList<>()
            .insertItem("Header")
            .insertList(viewModelObservableList)
            .insertItem("Foot");
    public final OnItemBind<Object> onItemBind = new OnItemBind<Object>() {
        @Override
        public void onItemBind(ItemBinding itemBinding, int position, Object item) {
            if (String.class.equals(item.getClass())) {
                itemBinding.set(BR.demoItemViewModel, R.layout.inputa_demo_item);
            } else if (TestMemberViewModel.class.equals(item.getClass())) {
                itemBinding.set(BR.demoItemViewModel, R.layout.inputa_demo_item);
            }
        }
    };
对应的xml中:

 <android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            binding:items="@{demoViewModel.headFooterItems}"
            binding:layoutManager="@{LayoutManagers.linear()}"
            binding:itemBinding="@{demoViewModel.onItemBind}"/>

上面的使用MergeObservableList替换原来的observableList,根据你的需要插入数据,插入头或尾巴;

onItemBind复写的方法中就可以根据你的类型不同加载不同的布局;


再看看绑定点击事件两种思路:

以前写adapter肯定都用过接口,下面方式一就可以通过接口回调到activity的ViewModel中,先定义一个接口:

public interface OnDemoListener{
        void onDemoItemClick(DemoEntity demoEntity);
    }
    public OnDemoListener onDemoListener = new OnDemoListener() {
        @Override
        public void onDemoItemClick(DemoEntity demoEntity) {
            
        }
    };
在上面设置onBinding的时候, 直接将listener传到item中:

public final OnItemBind<Object> onItemBind = new OnItemBind<Object>() {
        @Override
        public void onItemBind(ItemBinding itemBinding, int position, Object item) {
            if (String.class.equals(item.getClass())) {
                itemBinding.set(BR.demoItemViewModel, R.layout.inputa_demo_item);
            } else if (TestMemberViewModel.class.equals(item.getClass())) {
                itemBinding.set(BR.demoItemViewModel, R.layout.inputa_demo_item)
                        // 绑定点击事件
                        .bindExtra(BR.onDemoListener, onDemoListener);
            }
        }
    };

那么对应的item的xml变成如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="demoItemViewModel"
            type="com.reliable.inputa.ui.demo.DemoItemViewModel"/>

        <variable
            name="onDemoListener"
            type="com.reliable.inputa.ui.demo.DemoViewModel.OnDemoListener"/>

    </data>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:onClick="@{() -> onDemoListener.onDemoItemClick(demoItemViewModel)}">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{demoItemViewModel.observableField.get().getName()}"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{demoItemViewModel.observableField.get().getAddress()}"/>

    </LinearLayout>

</layout>

这是方式一,还有一种就是将item的点击事件返回给item的ViewModel,然后通过ItemViewModel回传或者设置一个观察者或者EventBus(不推荐)发给总的ViewModel;

开源的那个库中还有几种别的功能,这里不做介绍了,主要是ItemBinding的相关功能;


下周我也要放假回老家了,提前祝大家新年快乐。



猜你喜欢

转载自blog.csdn.net/geanwen/article/details/79257250