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是不是省了好多代碼呢。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • *本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發布 什么是MVVM 說到DataBinding,...
    帶心情去旅行閱讀 8,650評論 23 91
  • MVVM的發展歷程:MVC-->MVP-->MVVM。 MVVM是Model-View-ViewModel的簡寫。...
    瘋狂的木頭人閱讀 1,753評論 2 0
  • 一、概述 在 iOS 開發中,MVC(Model View Controller)是構建iOS App的標準模式,...
    CoderMikeHe閱讀 26,926評論 70 347
  • 當人們討論著直播的時候,我們在討論什么。討論主播的顏值,討論著大酥胸,偶爾露出大白腿。 在微博盛典會上,主播的顏值...
    AoA234閱讀 283評論 0 0
  • 最是深情的那一眼凝望 春,與你錯臂擦肩 你迷迷茫茫徘徊在未知的前路 受觀世音大師的點化 與人們同置身在酷熱的夏天 ...
    四川曹天成閱讀 181評論 0 0