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模式
- Modle即dataModle為抽象數據源為VIewModle提供數據
- 即通知viewModle響應事件
- 提供View顯示的數據流
這個是基本效果
我們先判斷登錄的密碼和用戶名 達到條件后就使用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是不是省了好多代碼呢。