Android MVVM(使用经验篇)

      MVVM的大名相信做手机开发的肯定不会陌生,我第一次听到它是从做IOS开发的同学那里听到的,我们的项目之前应用了MVP,要说服大家从MVP到MVVM,肯定得说说为啥,他优秀在那里?

      首先我们看看正常MVP的依赖关系图:

      这是个经典的MVP依赖关系,View 层和Presenter,Presenter和Model层彼此依赖,但是不会出现MVC那种跨层依赖,例如如果你写出来的View和Model层有依赖的话,那么就不是正常的MVP结构咯~这个结构好处很明显,Model和View层脱藕。因此修改Model层代码View层代码不用动~

      好了说了那么多MVP的,MVP是个很优秀的脱藕的架构。那么MVVM比MVP优秀在那里,还是那句,看图:
    
   

     我们看到,VM没了对View层的依赖~依赖的代码不由我们写是由框架代码生成~因此我们的进一步看到升级版的MVP~当然我知道最优秀当然是Spring~这里不离题了。MVVM为我们带来的就是这个,那么MVVM运用困难吗?我先跟你说:

     1.用了MVVM后,只要写一次后以后不用再写Adapter!

     2.用了MVVM后,只要写一次后以后不用再写Dialog!

     最重要是,Android官方提供了官方的针对MVVM开发的dataBinding . 所以主角出场,DataBinding!
    
    

     基本:

     然后我们说说配置,很简单一句话,在BuildGradle增加:

android {
    ....
    dataBinding {
        enabled = true
    }
}

   没有其他了,除了你的Android Studio 要超过1.3版本。

   DataBinding XML和我们平时有点不一样他,他的顶层必须是layout:

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

    <data>
        <variable
            name="vm"
            type="com.silver.testdatabinding.AMainPresenterVM" />

        <variable
            name="user"
            type="com.silver.model.BindingUser" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:orientation="vertical"
        >

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

        <Button
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_orange_light"
            android:gravity="center"
            android:layout_marginTop="20dp"
            android:onClick="@{()->vm.onClickInit()}"
            android:text="initList" />

         <Button
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_orange_light"
            android:gravity="center"
            android:layout_marginTop="20dp"
            android:onClick="@{()->vm.onClickTestUpdate()}"
            android:text="initList" />

        <ImageView
            android:id="@+id/image"
            android:layout_width="150dp"
            android:layout_height="150dp"
            bind:imageUrl="@{user.url}"/>

    </LinearLayout>
</layout>

 

      其中data标题表示数据,要绑定的数据,variable表示绑定变量,那么怎么绑定到某些Text或者什么的,上面代码也看到”@{}“加这个标志,譬如上面的Text我要显示User的Name的话,就直接user.name,当然这个name 是public ,那么马上有人问,我不喜欢这样,我是经过特殊处理的方法出来的字段,是否能显示,答案是可以的。例如user有过处理过Name的方法,叫getFirstName(),那么 TextView哪里要引用的话变成 以下:
      

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{user.getFirstName()}"/>

 

      除了字段显示字段这些我们还有很多的才能做到VM层不再依赖V层的,所以我们再看,点击事件回调,包括最普通的Onclick
      

<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="@{user.name}"
   android:onClick="@{()->vm.onClickUser(view)}"/>

 

      就如上面,是直接调用Lambda表达式,默认是省略了View 如果你要引用呢?

      

<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="@{user.name}"
   android:onClick="@{(view)->vm.onClickUser(view)}"/>

 

      当然其他View中有的点击事件也是类似炮制例如(onTextChange),这里不再多说~那如果木有定义过的事件呢?例如上面的 我要为ImageView设置 图片的URL?这里先留个悬念,下面说。因为那个是个人认为DataBinding非常牛逼有用的地方。

      接下来先说说在Activity怎么绑定这些~先看代码:

ActivityMainDataBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    binding = DataBindingUtil.setContentView(this ,R.layout.activity_main);
    AMainVM vm = new AMainVM();
    binding.setVm(vm);
    binding.setUser(vm.user);
}
@Override
protected void onDestroy() {
        //  这里不写也可以,写了加快回收~
        binding.unbind();
        super.onDestroy();
}

      其中 ActivityMainDataBinding,是自动生成的,生成规则是layout的名字去掉下划线并首字母大写加上DataBinding,不用clean ,rebuiding,写完XML就有~爽吧~然后开始绑定View,调用DataBindingUtil 的静态方法自动setContentView就自动绑定了View,然后调用进行进一步数据绑定,包括VM和其他数据。这里有人发现其实这样写不是累赘了吗?其实我们大可以不绑定User,而直接调用vm里的User。优化如下:

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

      then,我们的Activity只要绑定vm就可以咯~是不是觉得突然Activity什么都没了?我的项目也差不多这样的~

      再看看我们的User结构跟vm里面是啥:

public class BindingUser{
    public String name;
    public Int clickCount;
    public String url;
}

public class BindingUser{
    public  ObservableField<String> name = new ObservableField<>();
    public final ObservableInt clickCount = new ObservableInt();
    public final ObservableField<String> url = new ObservableField<>();
}

      这个是User类,其中上面的是我们这里用的,不需要动态更新的可以这样,但是实际上我们需要动态更新因此引入Observable观察者类,这样可以动态刷新,当我们改变其中的值得时候,View会得到及时的通知刷新界面。这里可能有同学又要问,我们平时都是用Gson啥的序列化进去,你这样写,我们就不能反序列化进去了,其实上面的BindingUser可以写到如下面的效果,看代码:

public class BindingUser extends BaseObservable{
    @Bindable
    public String name;
    @Bindable
    public Int clickCount;
    @Bindable
    public String url;

   public void setName(String name) {
       this.name= name;
       notifyPropertyChanged(BR.name);
   }
   public void setClickCount(String clickCount) {
       this.clickCount= clickCount;
       notifyPropertyChanged(BR.clickCount);
   }
   public void setUrl(String url) {
       this.url= url;
       notifyPropertyChanged(BR.url);
   }
}

 

      其中BaseObservable是基础观察者,我们首先需要继承他,否则之后的无效~然后在你需要动态刷新的字段上加上@Bindable 和写他们的set方法进行刷新,其中BR是DataBinding类动态生成的,对应每一个绑定变量variabl的ID值。

        再来看看VM里的代码:

 

public class AMainVM{

    private ObservableField<BindingUser> bindingUser;

    public AMainVM(){
        bindingUser = new ObservableField<>();
    }

    @Override
    public void onClickUser() {
        // 每点一此加1
        bindingUser.clickCount.set(bindingUser.clickCount.get() + 1);
    }

    @Override
    public void onClickInit() {
        BindingUser bindingUserModel = new BindingUser();
        bindingUser.name.set("just test");
        bindingUser.clickCount.set(0);
        bindingUser.url.set("http://img0.imgtn.bdimg.com/it/u=2770060730,47109478&fm=21&gp=0.jpg");
        bindingUser.set(bindingUserModel);
    }

    @Override
    public void onClickTestUpdate() {
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                bindingUser.name.set("update in thread");
                return null;
            }
        }.execute();
    }
}

      这里强烈推荐Observable这个类,超级方便,但是要注意的是,我为什么要再初始化VM的时候去初始化我们的User类的ObservableField,而不是点击的时候在方法onClickInit()中才懒加载?  那是因为如果那时我才初始化,会发现View界面木有任何变化。为啥?因为在Activity里绑定VM的时候,dataBinding会帮我们绑定了VM以及VM里面其他绑定在其他View的变量,所以那时如果你木有初始化VM内的变量需要绑定的变量,那么DataBinding会注销哪个对应BR ID的变量。因此,当你在下面再重新新建一个Observable的时候压根是没做过任何View的绑定注册,因此就算你怎么更新数据,View也不会更新,除非这时你持有View,然后强制重新绑定,但是这样就违背我们VM不能依赖View的原则,因此这里要注意初始化。

      大家都看到我还在下面做了个很邪恶的实验,在非UI线程更新BindingUser的name,验证下当我在非UI线程时候更新UI是否会出现问题,答案是不会。所以放心更新地操作数据,这里不多说里面实现了,无非就一个Handler嘛。

      这样像上面的就是一个最简单的MVVM的结构,what 's more?除了这些我们还想有其他控制,例如我想决定某个View是否显示,看如下代码:

<TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}"
            android:visibility="@{user.name == null ? View.GONE : View.VISIBLE}" />

     当然我们在引用View.Gone这个静态常量时候要在上面data里声明,应用import字段:

<data>
        ...
        <import type="android.view.View" />
</data>

      
     如果需要增加某个@String字段拼接我们的变量,或者我们自己随便写的一个String组拼那么怎样?

<TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            bind:message='@{"名字:" + user.name}'/>

<TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            bind:message="@{@string/name + user.name}"/>

 

    新增的特殊符号:
    我们平时遇到的,我们两个字段,谁不空显示谁,那么按我们平时写的如下:

<TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            bind:message="@{user.name == null? user.count:user.name}"/>

<TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            bind:message="@{user.name ?? user.count}"/>

     下面就是DataBinding提供的简化写法~用操作符??,上下意思两种表达意思一样。

     DataBinding的大概使用就是这样,当然还有很多我们探索的包括ObservableMap ,ObservableList这里使用也很方便,喜欢的同学自行查询~都很简单,以下介绍DataBinding最强大功能。

    自定义设置器(Custom setter):

     注释 @BindingAdapter ,为什么我不说BindMethod,因为我觉得作用不是太大,没使用,如果有同学觉得有大用,请一定告诉我~好继续,大家看到我开始的XML中有个很奇怪的字段,“bind:imageURL”这个是哪里来的?首先这个就是自定义设置器,首先我们要使用的话必须在定义如下:
     

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

        xmlns:bind="http://schemas.android.com/apk/res-auto">

       ......

    <ImageView
            android:id="@+id/image"
            android:layout_width="150dp"
            android:layout_height="150dp"
            bind:imageUrl="@{user.url}"/>
</layout>

       加上“xmlns:bind="http://schemas.android.com/apk/res-auto"” 有些同学会在之前的自定义View里用到“xmlns:app="http://schemas.android.com/apk/res-auto"”,这个没关系,将"bind:imageURL"改为"app:imageURL"就可以。那么我们定义了这个后还要写一个摆放这个imageUrl的处理方法,我自己起了一个类专门处理的,BindingUtil下面我们看看里面怎样的~

public class BindingUtil {

    @BindingAdapter({"imageUrl"})
    public static void loadImage(ImageView imageView, String imageUrl) {
        if (imageUrl != null) {
            Picasso.with(imageView.getContext()).load(imageUrl).into(imageView);
        }
    }

    @BindingAdapter({"showLoading", "message"})
    public static void showLoading(View view, boolean showLoading, String message) {
        if (showLoading) {
            // showLoading
        } else {
            // cancelLoading
        }
    }

    @BindingAdapter("showLoading")
    public static void showLoading(View view, boolean showLoading) {
        if (showLoading) {
            // showLoading
        } else {
            // cancelLoading
        }
    }
}

      做的不多方法头加上注解@BindingAdapter ,方法必须是静态方法哦,其中函数第一个参数是你绑定的View,这个函数调用的前提是ImageView里有个参数bind:imageUrl,当绑定的user.url初始化或者改变了值的时候会调用这个loadImage方法。因此我们VM里做数据转换变化,View马上就有响应去加载图片。VM不用管View更新问题~这个给了我启发,我们的ShowLoading,ShowToast是不是也可以这样,因此有了下面方法。注意:当方法中有多个参数的时候,例如上面的ShowLoading 有一个布尔的showLoading参数和message参数,随便一个更改参数都会调用我们设定的方法,因此我们写这里方法时需要特别注意参数处理哦~
       
      BaseRecycleAdapter:

       接下来我会说说我写的一个BaseRecycleAdapter主要针对RecycleView的,大家觉得好的可以直接拿去用,以后就不用再写关于RecycleView的Adapter了,当然对于ListView和Dialog?也是类似的思想~先说说思路:

       我们用DataBinding写一个Adapter会怎样?

public class MainAdapter extends BaseAdapter {

    private LayoutInflater layoutInflater;
    private List<String> listData;

    public MainAdapter(Activity activity,List<String> listData) {
        this.listData = listData;
        layoutInflater = LayoutInflater.from(activity);
    }

    @Override
    public int getCount() {
        if (getData() != null) {
            return getData().size();
        } else {
            return 0;
        }
    }

    @Override
    public Object getItem(int position) {
        if (getData() != null) {
            return getData().get(position);
        } else {
            return null;
        }
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        BindingHolder bindingHolder;
        if (convertView == null) {
            AdapterMainBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.adapter_main, null, false);
            bindingHolder = new BindingHolder();
            bindingHolder.setBinding(binding);
            convertView = binding.getRoot();
            convertView.setTag(bindingHolder);
        } else {
            bindingHolder = (BindingHolder) convertView.getTag();
        }

        bindingHolder.getBinding().setVariable(com.silver.testdatabinding.BR.testString, listData .get(position));
        bindingHolder.getBinding().executePendingBindings();
        return convertView;
    }

    public static class BindingHolder {
        private AdapterMainBinding binding;

        public AdapterMainBinding getBinding() {
            return binding;
        }

        public void setBinding(AdapterMainBinding binding) {
            this.binding = binding;
        }
    }
}

 

      首先我们写一个Holder,其中我们的Holder中其实只有一个binding,而binding在DataBindingUtil.inflate()时已经对ContentView进行了绑定,因此我们重新数据来刷新界面就可以,如上面setVariable然后再executePendingBindings()进行数据刷新操作。

       那么我们得思考一下,我们其实很多DataBinding的Adapter会非常类似,因此很有必要写一个通用,以后省去我们很多写Adapter的时间了~我们来考虑下一个Adapter会需要些什么。

        1.我们需要告诉Adapter我们contentView的LayoutId是啥。

        2.首先我们需要一个List ,滚动的时候动态绑定里面的每个Model进行数据刷新。

        3.我们需要告诉getView方法里我们需要绑定的Model 变量名 (BR的ID值)。

        4.我们要有个onItemClickListener作为事件点击。

        5.其他item里的onClick事件。  

        6.当List变化的时候通知Adapter进行notify。

    

      这些基本构成了我们Adapter的要求了~如果还有还可以自行添加~

      首先看看初始化方法

public class BaseRecycleViewAdapter<T> extends RecyclerView.Adapter<BaseRecycleViewAdapter.BindingHolder> {

    private static final Object DATA_INVALIDATION = new Object();
    public @LayoutRes ObservableInt layoutId;
    private AdapterModule adapterModule;

    private final WeakReferenceOnListChangedCallback<T> callback = new WeakReferenceOnListChangedCallback<>(this);
    private LayoutInflater inflater;

    // Currently attached recyclerview, we don't have to listen to notifications if null.
    @Nullable
    private RecyclerView recyclerView;

    public BaseRecycleViewAdapter(int layoutId) {
        this.layoutId = new ObservableInt(layoutId);
    }

    public void setAdapterModule(AdapterModule<T> adapterModule) {
        if (this.adapterModule == adapterModule || adapterModule == null) {
            return;
        }
        // If a recyclerview is listening, set up listeners. Otherwise wait until one is attached.
        // No need to make a sound if nobody is listening right?
        if (recyclerView != null) {
            if (this.adapterModule != null && this.adapterModule.list != null && this.adapterModule.list instanceof ObservableArrayList) {
                this.adapterModule.list.removeOnListChangedCallback(callback);
            }
            if (adapterModule != null && adapterModule.list != null && adapterModule.list instanceof ObservableList) {
                adapterModule.list.addOnListChangedCallback(callback);
            }
        }
        this.adapterModule = adapterModule;
        notifyDataSetChanged();
    }

    ......
}

     这是构造整个函数的基本参数,构造函数需要我们ContentView的LayoutId,而AdapterModule是针对Adapter的数据变量,其中对AdapterModule中list 进行addOnListChangedCallback 监听更新从而对Adapter进行刷新。Adapter是我们的数据变量组成类,我们来看看他内部:

     

public class AdapterModule<T> {

    public ObservableArrayList<T> list = new ObservableArrayList<>();
    // bindingVaiable id
    public ObservableInt bindingVaiable;
    // bind PositionId
    public ObservableInt bindPositionVaiableId;
    // the key is the resourceId!
    public WeakReference<SparseArray<OnClickListener>> listeners;

    public AdapterModule(ArrayList<T> list, int bindingVaiable,int bindPositionVaiableId) {
        if (list != null && !list.isEmpty()) {
            this.list.addAll(list);
        }
        this.bindingVaiable = new ObservableInt(bindingVaiable);
        this.bindPositionVaiableId = new ObservableInt(bindPositionVaiableId);
    }

    public AdapterModule(ArrayList<T> list, int bindingVaiable, SparseArray<OnClickListener> listeners) {
        if (list != null && !list.isEmpty()) {
            this.list.addAll(list);
        }
        this.bindingVaiable = new ObservableInt(bindingVaiable);
        this.listeners = new WeakReference<>(listeners);
    }

    public void setListeners(SparseArray<OnClickListener> listeners) {
        this.listeners = new WeakReference<>(listeners);
    }
}

     

      list就是我们显示数据,bindingVaiable是我们每个getView 里contentView要绑定的Module变量 ,第三个参数是个很有意思的参数,绑定当前getView的点position,只有BR指定的ID值,实际值是在Adapter中进行绑定。这里下面看代码~最后一个是我们的onClickListener。这个  onClickListener是我自己写的,其中也很简单只有一个带position参数的onClick方法。至于onClickListener绑定变量的BR值巧用了下SparserArray的Key就是我们绑定的变量BR值。

      我们继续看看Adapter的OnCreateViewHolder和OnBindHolder:
      

@Override
    public BindingHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (inflater == null) {
            inflater = LayoutInflater.from(parent.getContext());
        }

        ViewDataBinding binding = DataBindingUtil.inflate(inflater, layoutId.get(), null, false);
        final BindingHolder holder = new BindingHolder(binding, (SparseArray<OnClickListener>) adapterModule.listeners.get());

        binding.addOnRebindCallback(new OnRebindCallback() {
            @Override
            public boolean onPreBind(ViewDataBinding binding) {
                return recyclerView != null && recyclerView.isComputingLayout();
            }

            @Override
            public void onCanceled(ViewDataBinding binding) {
                if (recyclerView == null || recyclerView.isComputingLayout()) {
                    return;
                }
                int position = holder.getAdapterPosition();
                if (position != RecyclerView.NO_POSITION) {
                    notifyItemChanged(position, DATA_INVALIDATION);
                }
            }
        });
        return holder;
    }

    @Override
    public void onBindViewHolder(BindingHolder holder, int position) {
        // bindPosition
        onBindBinding(holder.binding, adapterModule.bindPositionVaiableId.get(), position, false);
        onBindBinding(holder.binding, adapterModule.bindingVaiable.get(), adapterModule.list.get(position), true);
    }

    private void onBindBinding(ViewDataBinding binding, int bindingVariable, Object item, boolean executePendingBindings) {
        if (bindingVariable != 0) {
            boolean result = binding.setVariable(bindingVariable, item);
            if (!result) {
                throw new IllegalArgumentException("can't bind variable adapterModule because can't find the id,is it correct?");
            }
            // refresh
            if (executePendingBindings) {
                binding.executePendingBindings();
            }
        }
    }

 

     onCreateViewHolder 里不多说了,主要增加了在binging进行时绑定检测是否recycleView已经在onAttach,在onBindView进行数据绑定第一个绑定的是position,setVariable(bindPosiionVaiableId,position);其中bindpositionVaiableId就是显示ContentView的position变量值,然后绑定当前position。同时重新绑定我们list中对应position的module变量进行绑定数据刷新,最后执行  binding.executePendingBindings();进行数据刷新。这就是一个recycleView绑定的数据过程。

      那么在哪里进行绑定OnClickListener?为甚么这里进行数据绑定还有要对position进行绑定?其实我们每个点击回调只需要一个回调变量函数,因此不用每次都进行绑定只要在ViewHolder里进行绑定,我们变化的只有Position。

      下面我们再看看ViewHolder代码:
      

public static class BindingHolder<T> extends RecyclerView.ViewHolder {

        ViewDataBinding binding;
        private SparseArray<OnClickListener> listeners;

        public BindingHolder(ViewDataBinding binding, SparseArray<OnClickListener> listeners) {
            super(binding.getRoot());
            this.binding = binding;
            this.listeners = listeners;
            onBindListeners();
        }

        public void onBindListeners() {
            if (listeners != null && listeners.size() > 0) {
                for (int i = 0; i < listeners.size(); i++) {
                    binding.setVariable(listeners.keyAt(i), listeners.get(listeners.keyAt(i)));
                }
            }
        }
    }

     最后当我们List进行刷新的时候需要对我们的Adapter进行刷新,因此看看OnListChangeListener代码:
    

private static class WeakReferenceOnListChangedCallback<T> extends ObservableArrayList.OnListChangedCallback<ObservableArrayList<T>> {
        final WeakReference<BaseRecycleViewAdapter<T>> adapterRef;

        WeakReferenceOnListChangedCallback(BaseRecycleViewAdapter<T> adapter) {
            this.adapterRef = new WeakReference<>(adapter);
        }

        @Override
        public void onChanged(ObservableArrayList<T> ts) {
            BaseRecycleViewAdapter<T> adapter = adapterRef.get();
            if (adapter == null) {
                return;
            }
            Utils.ensureChangeOnMainThread();
            adapter.notifyDataSetChanged();
        }

        ...
    }

      主要是数据更改是对Adapter进行notify。       

      这样看还是有点抽象,我们再看看怎么用,就一目了然。
      

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

       <variable
            name="position"
            type="Integer" />

        <variable
            name="user"
            type="com.silver.model.BindingUser" />

         <variable
            name="onItemClick"
            type="com.ykse.mvvm.adapter.listener" />
         <variable
            name="onClick"
            type="com.ykse.mvvm.adapter.listener" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:orientation="vertical"
        android:onClick="@{()->onItemClick.onClick(position)}"
        >

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

        <Button
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_orange_light"
            android:gravity="center"
            android:layout_marginTop="20dp"
            android:onClick="@{()->onClick.onClick(position)}"
            android:text="initList" />

        <ImageView
            android:id="@+id/image"
            android:layout_width="150dp"
            android:layout_height="150dp"
            bind:imageUrl="@{user.url}"/>

    </LinearLayout>
</layout>

 

     这个就是我们写的Adapter的ContentView。我们的点击事件,还有变量还有OnItemClick都解决了。

     还有那么我们的Activity的ContentView是怎么将AdapterModule和layoutId跟recycleView绑定的?看看XML:
    

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

        <variable
            name="layoutId"
            type="Integer" />

        <variable
            name="vm"
            type="com.silver.model.AMainVM" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:orientation="vertical"
        >

        <android.support.v7.widget.RecyclerView
            android:id="@+id/id_recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            bind:layoutId="@{layoutId}"
            bind:listData="@{vm.adapterModule}"/>

    </LinearLayout>
</layout>

     这次@BindingAdapter又发挥作用~
    

public class BindingAdapterUtil {

    @BindingAdapter({"layoutId"})
    public static void bindRecycleViewLayoutId(RecyclerView recyclerView, int layoutId) {
        BaseRecycleViewAdapter baseRecycleViewAdapter = new BaseRecycleViewAdapter(layoutId);
        recyclerView.setAdapter(baseRecycleViewAdapter);
    }

    @BindingAdapter({"listData"})
    public static <T> void bindRecycleViewAdapterModule(RecyclerView recyclerView, AdapterModule<T> adapterModule) {
        BaseRecycleViewAdapter<T> adapter;
        if (recyclerView.getAdapter() != null) {
            adapter = (BaseRecycleViewAdapter<T>) recyclerView.getAdapter();
            adapter.setAdapterModule(adapterModule);
        } else {
            return;
        }
    }
}

 

      还有更多的双向绑定~大家可以参考下资料,原本DataBinding也有一些原生的~

      到这里我们DataBinding的使用篇算是完了,更多的功能需要大家更多发现~谢谢能看到这里的人,你们都是热爱技术的人!

时间: 2024-11-05 16:23:50

Android MVVM(使用经验篇)的相关文章

移动开发每周阅读清单:iOS多线程安全、构建Android MVVM应用框架

(我进去瞅了一眼又退出了.) 『移动开发每周阅读清单』第三十七期与大家见面了,上周支付宝来抢头条了,我想事情变成这样不是他们的本意,只能说产品经理还是很重要啊. 提示:点击文末阅读原文可打开带链接的版本. 提示2:文末有小福利~ 新闻 Apple 停止了 AirPort 产品线开发 根据彭博社报道,Apple 已经停止包括 AirPort Express.AirPort Extreme 等无线路由产品的开发.Apple 希望可以将人手用在带来收益更高的下一代苹果产品中.不过并不清楚苹果会在什么时

如何构建Android MVVM应用程序

1.概述 Databinding 是一种框架,MVVM是一种模式,两者的概念是不一样的.我的理解DataBinding是一个实现数据和UI绑定的框架,只是一个实现MVVM模式的工具.ViewModel和View可以通过DataBinding来实现单向绑定和双向绑定,这套UI和数据之间的动态监听和动态更新的框架Google已经帮我们做好了.在MVVM模式中ViewModel和View是用绑定关系来实现的,所以有了DataBinding 使我们构建Android MVVM 应用程序成为可能. 之前看

Android MVVM 应用框架构建过程详解

概述 说到Android MVVM,相信大家都会想到Google 2015年推出的DataBinding框架.然而两者的概念是不一样的,不能混为一谈.MVVM是一种架构模式,而DataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个工具. 之前看过很多关于Android MVVM的博客,但大多数提到的都是DataBinding的基本用法,很少有文章仔细讲解在Android中是如何通过DataBinding去构建MVVM的应用框架的.View.ViewModel.Model每

Gradle for Android 第三篇( 依赖管理 )

Gradle for Android 第三篇( 依赖管理 ) 依赖管理是Gradle最闪耀的地方,最好的情景是,你仅仅只需添加一行代码在你的build文件,Gradle会自动从远程仓库为你下载相关的jar包,并且保证你能够正确使用它们.Gradle甚至可以为你做的更多,包括当你在你的工程里添加了多个相同的依赖,gradle会为你排除掉相同的jar包. 作者:佚名来源:Android开发中文站|2017-04-10 17:35  移动端  收藏   分享 依赖管理 依赖管理是Gradle最闪耀的地

Android基础总结篇之三:Activity的task相关介绍_Android

本篇文章主要介绍了android基础总结篇之三:Activity的task相关,具有一定的参考价值,有需要的可以了解一下. 今天我们来讲一下Activity的task相关内容. 上次我们讲到Activity的四种启动模式的时候,已经了解到一些关于task的技术,今天我再向大家介绍一下.task是一个具有栈结构的容器,可以放置多个Activity实例.启动一个应用,系统就会为之创建一个task,来放置根Activity:默认情况下,一个Activity启动另一个Activity时,两个Activi

Android基础总结篇之三:Activity的task相关介绍

本篇文章主要介绍了android基础总结篇之三:Activity的task相关,具有一定的参考价值,有需要的可以了解一下. 今天我们来讲一下Activity的task相关内容. 上次我们讲到Activity的四种启动模式的时候,已经了解到一些关于task的技术,今天我再向大家介绍一下.task是一个具有栈结构的容器,可以放置多个Activity实例.启动一个应用,系统就会为之创建一个task,来放置根Activity:默认情况下,一个Activity启动另一个Activity时,两个Activi

android基础总结篇之八:创建及调用自己的ContentProvider_Android

今天我们来讲解一下如何创建及调用自己的ContentProvider. 在前面两篇文章中我们分别讲了如何读写联系人和短消息,相信大家对于ContentProvider的操作方法已经有了一定程度的了解.在有些场合,除了操作ContentProvider之外,我们还有可能需要创建自己的ContentProvider,来提供信息共享的服务,这就要求我们很好的掌握ContentProvider的创建及使用技巧.下面我们就由表及里的逐步讲解每个步骤. 在正式开始实例演示之前,我们先来了解以下两个知识点:

android基础总结篇之一:Activity生命周期_Android

近来回顾了一下关于Activity的生命周期,参看了相关书籍和官方文档,也有了不小的收获,对于以前的认知有了很大程度上的改善,在这里和大家分享一下. 熟悉javaEE的朋友们都了解servlet技术,我们想要实现一个自己的servlet,需要继承相应的基类,重写它的方法,这些方法会在合适的时间被servlet容器调用.其实Android中的Activity运行机制跟servlet有些相似之处,Android系统相当于servlet容器,Activity相当于一个servlet,我们的Activi

android基础总结篇之八:创建及调用自己的ContentProvider

今天我们来讲解一下如何创建及调用自己的ContentProvider. 在前面两篇文章中我们分别讲了如何读写联系人和短消息,相信大家对于ContentProvider的操作方法已经有了一定程度的了解.在有些场合,除了操作ContentProvider之外,我们还有可能需要创建自己的ContentProvider,来提供信息共享的服务,这就要求我们很好的掌握ContentProvider的创建及使用技巧.下面我们就由表及里的逐步讲解每个步骤. 在正式开始实例演示之前,我们先来了解以下两个知识点: