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