MVVM设计模式

MVVM设计模式

在介绍MVVM设计模式之前我们先介绍一下DataBinding

DataBinding,2015年IO大会介绍的一个框架,字面理解即为数据绑定,是Google对MVVM在Android上的一种实现,可以直接绑定数据到xml中,并实现自动刷新。

好处:

  • 去掉大部分UI相关代码(比如findViewById、setOnClickListener、setText等)
  • xml变成UI的唯一真实来源,数据绑定也直接发生在xml

首先我们要在build.gradle(app)的android里添加

  dataBinding {
        enabled = true
    }

然后我们在Activity创建的layout里面添加<layout> ,<data> 两个标签

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

    <data>

        <variable
            name="bean"
            type="com.example.andy.mvvmtest.bean"/>
    </data>
<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.andy.mvvmtest.MainActivity">

    <TextView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:text="@{bean.name}"
         />

</LinearLayout>
</layout>

我们先用layout包围

  • 注意我们在data里面声明了一个变量 他是 com.example.andy.mvvmtest.bean的变量
  • 然后我们在Textview里面设置text使用了bean这个变量的name属性

这是我bean的代码

public class bean {
    public  String name;

    public bean(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

这就是一个普通的javabean

接着 我们在MianActivity使用DataBindUtil来setContentView
然后系统会自动生成一个layout名对应的Binding如activity_main 则生成了AcitivityMianBinding
然后我们使用这个Binding来setTextview对应的bean

public class MainActivity extends AppCompatActivity {
    ActivityMainBinding binding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding=DataBindingUtil.setContentView(this,R.layout.activity_main);
        bean bean1=new bean("hhhhhh");
        binding.setBean(bean1);
        bean1.setName("asdfasdf");
    }
}

这样我们就完成了dataBindin入门

接下来我们说一下MVVM模式


image
  • Modle即dataModle为抽象数据源为VIewModle提供数据
  • 即通知viewModle响应事件
  • 提供View显示的数据流

这个是基本效果

MVVMTest.gif

我们先判断登录的密码和用户名 达到条件后就使用Retrofit请求 然后得到一个刷新列表。

我们先来展示一下基本MVVM模式的代码
首先我们定义一个ViewModle接口


public interface ViewModle {
    public void destroy();
}

这里只定义一个destory的接口

这是MainActivity的layout

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

    <data>

        <variable
            name="bean"
            type="com.example.andy.mvvmtest.MainVIewModle"/>
    </data>
<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.andy.mvvmtest.MainActivity">

    <EditText
        android:layout_margin="10dp"
        android:background="@drawable/editext"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="@{bean.name}"
        android:visibility="@{bean.nameVisiable}"
        app:addTextChangedListener="@{bean.getnameTextWatcher}"
        />
    <EditText
        android:layout_margin="10dp"
        android:background="@drawable/editext"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="@{bean.password}"
        android:visibility="@{bean.pswVisiable}"
        app:addTextChangedListener="@{bean.getpswTextWatcher}"
        />
    <Button
        android:textColor="@android:color/white"
        android:text="@string/login"
        android:layout_margin="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="@{bean.buttonVisiable}"
        android:background="@drawable/click_button"
        android:enabled="@{bean.buttonEnable}"
        android:onClick="@{bean::OnclickChange}"
        android:id="@+id/button"/>
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycleview"
        android:visibility="@{bean.recycleVisiable}"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

</layout>

我们先来实现以下MainViewModle

public class MainVIewModle implements ViewModle {
    private static final String TAG = "MainVIewModle";
    public ObservableField<String> name;
    public ObservableField<String> password;
    public ObservableInt nameVisiable;
    public ObservableInt pswVisiable;
    public ObservableInt buttonVisiable;
    public ObservableBoolean buttonEnable;
    public ObservableInt recycleVisiable;
    private Context context;
    private DataListner listner;
    private Disposable disposable;


    public MainVIewModle(Context context, DataListner listner) {
        this.context = context;
        this.listner = listner;
        nameVisiable = new ObservableInt(View.VISIBLE);
        pswVisiable = new ObservableInt(View.VISIBLE);
        buttonVisiable = new ObservableInt(View.VISIBLE);
        recycleVisiable = new ObservableInt(View.GONE);
        password = new ObservableField<>("");
        name = new ObservableField<>("");
        buttonEnable = new ObservableBoolean(false);

    }

    @Override
    public void destroy() {
        context = null;
        listner = null;
        if(disposable!=null)
        disposable.dispose();
    }

    interface DataListner {
        public void OndateChage(List<ListBean> list);
    }

    public TextWatcher getnameTextWatcher() {
        return new TextWatcher() {

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                Log.d(TAG, "onTextChanged: " + " " + s.length() + password.get().length());
                if (s.length() > 4 && password.get().length() > 7)
                    buttonEnable.set(true);
                else
                    buttonEnable.set(false);
            }

            @Override
            public void afterTextChanged(Editable s) {
                name.set(s.toString());
            }
        };
    }

    public TextWatcher getpswTextWatcher() {
        return new TextWatcher() {

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                Log.d(TAG, "onTextChanged: " + s.length() + " " + name.get().length());
                if (s.length() > 7 && name.get().length() > 4)
                    buttonEnable.set(true);
                else
                    buttonEnable.set(false);
            }

            @Override
            public void afterTextChanged(Editable s) {
                password.set(s.toString());
            }
        };
    }

    public void OnclickChange(View view) {

        Log.d(TAG, "OnclickChange: " + view.getId());
        Toast.makeText(view.getContext(), "hhhhh", Toast.LENGTH_SHORT).show();
        login();
    }

    public void login() {
        User user = new User();
        user.setName(name.get());
        user.setPassword(password.get());
        recycleVisiable.set(View.VISIBLE);
        nameVisiable.set(View.GONE);
        pswVisiable.set(View.GONE);
        buttonVisiable.set(View.GONE);
        LoginService service = LoginService.Factory.create();
        service.dologin(user)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(new Observer<List<ListBean>>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        disposable=d;
                    }

                    @Override
                    public void onNext(List<ListBean> value) {

                    }

                    @Override
                    public void onError(Throwable e) {
                        List<ListBean> list=new ArrayList<ListBean>();
                        for(int i=0;i<10;i++)
                        {
                            ListBean bean=new ListBean();
                            bean.setTitle("On The NO"+i);
                            bean.setSummary("This is No"+i+"News");
                            bean.setWatchers(10+i);
                            bean.setForks(5+i);
                            bean.setForks(3+i);
                            list.add(bean);
                        }
                        listner.OndateChage(list);
                    }

                    @Override
                    public void onComplete() {

                    }
                });
        List<ListBean> list=new ArrayList<ListBean>();
                        for(int i=0;i<10;i++)
                        {
                            ListBean bean=new ListBean();
                            bean.setTitle("On The NO"+i);
                            bean.setSummary("This Is No"+i+"News");
                            bean.setWatchers(10+i);
                            bean.setForks(5+i);
                            bean.setForks(3+i);
                            list.add(bean);
                        }
                        listner.OndateChage(list);

    }
}

这里的getnameTextWatcher 和 getpswTextWatcher 与上面的layout相对应 分别判断 这里的密码和用户名是否符合要求,符合则将 登录的button 设为可以点击 login就是执行登录操作。然后我们要在desotry哪里将 disposable.dispose();防止页面被销毁 时候还在请求。

下面在看一下我们的modle

public class User {
    public String name;
    public String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

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

modle 只有两个简单的参数

public interface LoginService {
    @FormUrlEncoded
    @POST("users/{username}/repos")
    Observable<List<ListBean>> dologin(@Body User username);


    class Factory {
        public static LoginService create() {
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://192.168.0.1:8080/")
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
            return retrofit.create(LoginService.class);
        }
    }
}

MainActivity

public class MainActivity extends AppCompatActivity implements MainVIewModle.DataListner{
    public volatile int a=1;
    private static final String TAG="MainActivity";
    ActivityMainBinding binding;
    Handler h;
    MyAdapter myAdapter;
    MainVIewModle mainVIewModle;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding=DataBindingUtil.setContentView(this,R.layout.activity_main);
        mainVIewModle= new MainVIewModle(this,this);
        bean bean1=new bean(new ObservableField<String>("JJJJJJ"));
        binding.setBean(mainVIewModle);
        myAdapter=new MyAdapter();
        binding.recycleview.setAdapter(myAdapter);
        binding.recycleview.setLayoutManager(new LinearLayoutManager(this));
    }


    @Override
    public void OndateChage(List<ListBean> list) {
        myAdapter.setList(list);
        myAdapter.notifyDataSetChanged();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mainVIewModle.destroy();
    }
}

在MainActivity里面我们只需要初始化Recycleview的Adapter和在Destory的时候调用MainVIewModle的Dstory

这个是RecycleView的Item 和Activity_main差不多 就不展开说了。

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

    <data>

        <variable
            name="viewModel"
            type="com.example.andy.mvvmtest.ItemVIewModle" />
    </data>

    <android.support.v7.widget.CardView
        android:id="@+id/card_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="@dimen/vertical_margin_half"
        android:layout_marginLeft="@dimen/vertical_margin"
        android:layout_marginRight="@dimen/vertical_margin"
        android:layout_marginTop="@dimen/vertical_margin_half"
        card_view:cardCornerRadius="2dp">

        <LinearLayout
            android:id="@+id/layout_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="?attr/selectableItemBackground"
            android:onClick="@{viewModel.onItemClick}"
            android:orientation="vertical">

            <TextView
                android:id="@+id/text_repo_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:maxLines="1"
                android:paddingLeft="12dp"
                android:paddingRight="12dp"
                android:paddingTop="12dp"
                android:text="@{viewModel.Title}"
                android:textSize="20sp"
                tools:text="Repository Name" />

            <TextView
                android:id="@+id/text_repo_description"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingBottom="12dp"
                android:paddingLeft="12dp"
                android:paddingRight="12dp"
                android:paddingTop="10dp"
                android:text="@{viewModel.Summary}"
                android:textSize="14sp"
                tools:text="This is where the repository description will go" />

            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="@android:color/black" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:orientation="horizontal">

                <TextView
                    android:id="@+id/text_watchers"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:text="@{viewModel.watchers}"
                    tools:text="10 \nWatchers" />

                <TextView
                    android:id="@+id/text_stars"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:text="@{viewModel.stars}"

                    tools:text="230 \nStars" />

                <TextView
                    android:id="@+id/text_forks"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:text="@{viewModel.forks}"

                    tools:text="0 \nForks" />

            </LinearLayout>

        </LinearLayout>

    </android.support.v7.widget.CardView>

</layout>

下面是RecycleView的Adapter

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    List<ListBean> mylist;
    public MyAdapter()
    {
        mylist= Collections.emptyList();
    }
    public void MyAdapter(List<ListBean> list)
    {
        this.mylist=list;
    }
    public void setList(List<ListBean> list)
    {
        this.mylist=list;
    }
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
      RecycleviewItemBinding binding= DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),R.layout.recycleview_item,parent,false);

        return new MyViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.bindRepository(mylist.get(position));
    }

    @Override
    public int getItemCount() {
        return mylist.size();
    }

    class MyViewHolder extends RecyclerView.ViewHolder{

        final RecycleviewItemBinding binding;

        public MyViewHolder(RecycleviewItemBinding binding) {
            super(binding.cardView);
            this.binding = binding;
        }
        void bindRepository(ListBean repository) {
            if (binding.getViewModel() == null) {
                binding.setViewModel(new ItemVIewModle(itemView.getContext(), repository));
            } else {
                binding.getViewModel().setListBean(repository);
            }
        }
    }

}

我们的ViewHolder并不是使用Itemview来findViewById 而是直接传入一个Binding然后在bindRepository里面绑定数据,假如这个Item已经创建 那么直接set那个bean

这个是RecycleView的ViewModle

public class ItemVIewModle extends BaseObservable implements ViewModle {
    private Context context;
    private ListBean bean;
    public ItemVIewModle(Context context, ListBean repository) {
        this.context=context;
        this.bean=repository;
    }

    @Override
    public void destroy() {

    }
    public void setListBean(ListBean bean)
    {

    }
    public String getTitle()
    {
        if(bean!=null)
            return bean.getTitle();
        else
            return "";
    }
    public String getSummary()
    {
        if(bean!=null)
            return bean.getSummary();
        else
            return "";
    }
    public String getWatchers()
    {
        if(bean!=null)
            return bean.getWatchers()+"";
        else
            return "";
    }
    public String getStars()
    {
        if(bean!=null)
            return bean.getStars()+"";
        else
            return "";
    }
    public String getForks()
    {
        if(bean!=null)
            return bean.getForks()+"";
        else
            return "";
    }
    public void onItemClick(View view)
    {

    }
}

这个和layout是对应的

这个是listItem的Modle

public class ListBean implements Parcelable {
    private String Title;
    private String Summary;
    private int watchers;
    private int  stars;
    private int forks;
    public void onItemClick(View view)
    {

    }

    public String getTitle() {
        return Title;
    }

    public void setTitle(String title) {
        Title = title;
    }

    public String getSummary() {
        return Summary;
    }

    public void setSummary(String summary) {
        Summary = summary;
    }

    public int getWatchers() {
        return watchers;
    }

    public void setWatchers(int watchers) {
        this.watchers = watchers;
    }

    public int getStars() {
        return stars;
    }

    public void setStars(int stars) {
        this.stars = stars;
    }

    public int getForks() {
        return forks;
    }

    public void setForks(int forks) {
        this.forks = forks;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.Title);
        dest.writeString(this.Summary);
        dest.writeInt(this.watchers);
        dest.writeInt(this.stars);
        dest.writeInt(this.forks);
    }

    public ListBean() {
    }

    protected ListBean(Parcel in) {
        this.Title = in.readString();
        this.Summary = in.readString();
        this.watchers = in.readInt();
        this.stars = in.readInt();
        this.forks = in.readInt();
    }

    public static final Parcelable.Creator<ListBean> CREATOR = new Parcelable.Creator<ListBean>() {
        @Override
        public ListBean createFromParcel(Parcel source) {
            return new ListBean(source);
        }

        @Override
        public ListBean[] newArray(int size) {
            return new ListBean[size];
        }
    };
}

这样我们就完成了基本的MVVM模式框架,MVVM框架现对于MVP是不是省了好多代码呢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 什么是MVVM 说到DataBinding,...
    带心情去旅行阅读 8,591评论 23 91
  • MVVM的发展历程:MVC-->MVP-->MVVM。 MVVM是Model-View-ViewModel的简写。...
    疯狂的木头人阅读 1,736评论 2 0
  • 一、概述 在 iOS 开发中,MVC(Model View Controller)是构建iOS App的标准模式,...
    CoderMikeHe阅读 26,821评论 76 347
  • 当人们讨论着直播的时候,我们在讨论什么。讨论主播的颜值,讨论着大酥胸,偶尔露出大白腿。 在微博盛典会上,主播的颜值...
    AoA234阅读 263评论 0 0
  • 最是深情的那一眼凝望 春,与你错臂擦肩 你迷迷茫茫徘徊在未知的前路 受观世音大师的点化 与人们同置身在酷热的夏天 ...
    四川曹天成阅读 171评论 0 0