MVVM设计模式,在此处就不介绍,网上有很多大牛都有很详细的讲解。
相信大家看了MVVM设计模式后,不太清楚到底应该怎么写。本人不才,现将个人写的一个小Demo分享给大家,请大家批评指针。
如图所示:
项目的整体结构:
运行的效果:
第一部分:写一写BaseX相关的东西
因为使用MVVM,V中负责显示,ViewModel需要持有一个View的实例(不是binding).
- IBaseView(显示的一些公共的部分)
public interface IBaseView {
// 显示错误提示
void showError(String message);
//显示成功提示
void showSuccess(String message);
//显示正在加载
void showLoading();
void complete();
}
- IBaseVM(ViewModel的Base)
因为ViewModel需要持有一个IBaseView的实例,(当从Model中获取数据后,或者显示错误/显示数据获取完成等需要使用接口的方式反馈给View)
public interface IBaseVM<V extends IBaseView> {
void attachView(V view);
void detachView();
V getIView();
}
- BaseVM (通过attachView和detachView的方式,获取BaseView的引用和解除)
public abstract class BaseVM<V extends IBaseView> implements IBaseVM{
private WeakReference<IBaseView> mVWeakReference;
@Override
public void attachView(IBaseView view) {
mVWeakReference=new WeakReference<>(view);
}
@Override
public void detachView() {
if (mVWeakReference != null) {
mVWeakReference.clear();
mVWeakReference=null;
}
}
@Override
public IBaseView getIView() {
if (mVWeakReference != null) {
return mVWeakReference.get();
}
return null;
}
}
- BaseBindingAdapter
在使用recyclerview中,难免会写适配器adapter,此处将上拉加载,下拉刷新进行封装。
public abstract class BaseBindingAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
public List<T> mDataList;
public Context mContext;
public BaseBindingAdapter(Context context) {
mContext = context;
mDataList = new ArrayList<>();
}
@Override
public int getItemCount() {
return mDataList.size();
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
return onCreateVH(parent, viewType);
}
@Override
public void onBindViewHolder(VH holder, int position) {
onBindVH(holder, position);
}
/**
* Author: MrGao
* CreateTime: 2018/4/6 17:12
* MethodName:
* Des:刷新数据
* Params:
* Return:
**/
public void onRefreshData(List<T> list) {
if (mDataList != null) {
mDataList.clear();
mDataList.addAll(list);
notifyDataSetChanged();
}
}
public void onLoadMoreData(List<T> list) {
if (mDataList != null && list != null) {
mDataList.addAll(list);
notifyDataSetChanged();
}
}
public abstract VH onCreateVH(ViewGroup parent, int viewType);
public abstract void onBindVH(VH holder, int position);
}
- BaseBindViewHolder
在写recyclerview的viewholder的时候,和以前不一样了,应该使用binding的方式,在adapter中通过
DataBindingUtil.inflate()的方式获取到binding对象。
public class BaseBindViewHolder extends RecyclerView.ViewHolder {
ViewDataBinding mDataBinding;
public BaseBindViewHolder(ViewDataBinding binding) {
super(binding.getRoot());
mDataBinding=binding;
}
public ViewDataBinding getBinding() {
return mDataBinding;
}
}
第二部分:MVVM的View
MVVM的View,在activity中用于显示数据的作用 ,采用了DataBindingUtil加载布局并获取Binding对象。此处的下拉刷新和上拉加载使用了本人自己封装的LucklyRecyclerView库。(欢迎大家star)。
public class MainActivity extends AppCompatActivity implements MainBaseView, LucklyRecyclerView.OnLoadMoreListener, LucklyRecyclerView.OnRefreshListener {
private ActivityMainBinding mMainBinding;
private MainDataAdapter mDataAdapter;
private MainViewModelImpl mMainViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
initView();
}
/**
* 初始化布局
*/
private void initView() {
mDataAdapter = new MainDataAdapter(this);
mMainBinding.recylerView.setLayoutManager(new LinearLayoutManager(this));
mMainBinding.recylerView.setLoadMoreListener(this);
mMainBinding.recylerView.setOnRefreshListener(this);
mMainBinding.recylerView.setAdapter(mDataAdapter);
if (mMainViewModel == null) {
mMainViewModel = new MainViewModelImpl();
}
mMainViewModel.attachView(this);//将IBaseView对象传递给ViewModel,这样ViewModel才能使用接口的方式反馈数据给View
mMainViewModel.setDataAdapter(mDataAdapter);
mMainViewModel.refresh();
}
/**
* 显示错误信息
*
* @param message
*/
@Override
public void showError(String message) {
}
/**
* Author: MrGao
* CreateTime: 2018/4/6 16:22
* MethodName:
* Des:显示成功信息
* Params:
* Return:
**/
@Override
public void showSuccess(String message) {
}
/**
* Author: MrGao
* CreateTime: 2018/4/6 16:22
* MethodName: 显示正在加载等信息
* Des:
* Params:
* Return:
**/
@Override
public void showLoading() {
}
@Override
public void onLoadMore() {
mMainBinding.recylerView.setLoading();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mMainViewModel.loadMore();
}
}, 1200);
}
@Override
public void complete() {
mMainBinding.recylerView.setLoadingComplete();
mMainBinding.recylerView.setRefreshComplete();
}
@Override
public void onRefresh() {
mMainBinding.recylerView.setRefreshEnable(true);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mMainViewModel.refresh();
}
}, 1200);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mMainViewModel != null) {
mMainViewModel.detachView();
}
}
}
if (mMainViewModel == null) {
mMainViewModel = new MainViewModelImpl();
}
mMainViewModel.attachView(this);//将IBaseView对象传递给ViewModel,这样ViewModel才能使用接口的方式反馈数据给View
mMainViewModel.setDataAdapter(mDataAdapter);
mMainViewModel.refresh();
}
/**
* 显示错误信息
*
* @param message
*/
@Override
public void showError(String message) {
}
/**
* Author: MrGao
* CreateTime: 2018/4/6 16:22
* MethodName:
* Des:显示成功信息
* Params:
* Return:
**/
@Override
public void showSuccess(String message) {
}
/**
* Author: MrGao
* CreateTime: 2018/4/6 16:22
* MethodName: 显示正在加载等信息
* Des:
* Params:
* Return:
**/
@Override
public void showLoading() {
}
@Override
public void onLoadMore() {
mMainBinding.recylerView.setLoading();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mMainViewModel.loadMore();
}
}, 1200);
}
@Override
public void complete() {
mMainBinding.recylerView.setLoadingComplete();
mMainBinding.recylerView.setRefreshComplete();
}
@Override
public void onRefresh() {
mMainBinding.recylerView.setRefreshEnable(true);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mMainViewModel.refresh();
}
}, 1200);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mMainViewModel != null) {
mMainViewModel.detachView();
}
}
}
activity_main.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical">
<com.mrgao.luckrecyclerview.LucklyRecyclerView
android:layout_width="match_parent"
android:id="@+id/recylerView"
android:layout_height="match_parent">
</com.mrgao.luckrecyclerview.LucklyRecyclerView>
</LinearLayout>
</layout>
- 适配器的编写
/**
* Created by mr.gao on 2018/4/6.
* Package: com.gao.mvvmdemo.mvvm.adapter
* Create Date:2018/4/6
* Project Name:MVVMDemo
* Description:
*/
public class MainDataAdapter extends BaseBindingAdapter<ShowDataBeans,BaseBindViewHolder> {
public MainDataAdapter(Context context) {
super(context);
}
@Override
public BaseBindViewHolder onCreateVH(ViewGroup parent, int viewType) {
ViewDataBinding binding=DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.main_item,parent,false);
return new BaseBindViewHolder(binding);
}
@Override
public void onBindVH(BaseBindViewHolder holder, int position) {
ViewDataBinding binding=holder.getBinding();
binding.setVariable(BR.databeans,mDataList.get(position));//绑定数据
binding.executePendingBindings();//防止闪烁
}
}
binding.setVariable(BR.databeans,mDataList.get(position));//绑定数据
binding.executePendingBindings();//防止闪烁
}
}
.main_item
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/tools"
>
<data>
<variable
name="databeans"
type="com.gao.mvvmdemo.mvvm.beans.ShowDataBeans"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="80dp"
bind:imageUrl="@{databeans.images[0]}"
bind:error="@{@drawable/ic_launcher}"
android:layout_height="80dp"/>
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:textStyle="bold"
android:text="@{databeans.source}"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_marginTop="10dp"
android:text="@{databeans.desc}"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</layout>
bind:imageUrl="@{databeans.images[0]}"
bind:error="@{@drawable/ic_launcher}"
android:layout_height="80dp"/>
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:textStyle="bold"
android:text="@{databeans.source}"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_marginTop="10dp"
android:text="@{databeans.desc}"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</layout>
上面标红的部分使用的是自定义的加载图片方法:
public class ImageHelper {
/**
* 注意:第三个参数为图片显示错误的时候,此处为drawable对象,
* 那么在xml文件中就应该为@drawable/xxxx,假设此处使用errorDra为int类型则会报错:
* Error:(22, 29) Cannot find the setter for attribute 'app:imageUrl' with para
* @param imageView
* @param url
* @param errorDra
*/
@BindingAdapter(value = {"bind:imageUrl","bind:error"})
public static void loadImages(ImageView imageView,String url,Drawable errorDra){
Glide.with(imageView.getContext()).load(url).error(errorDra).centerCrop().into(imageView);
}
}
第三部分:Model中使用retrofit+httpclient的方式访问数据
访问数据请在AndroidManifest.xml中添加网络权限
此处有RxJava2.0相关的知识就不讲解咯, 。
- HttpUtils:访问前肯定的做一些配置吧,比如超时重连,连接的时长等。此处的create使用的泛型,是为了复用,在请求URL的时候我们只需要传递一个class即可
public class HttpsUtils {
OkHttpClient mClient;
private static HttpsUtils sHttpsUtils;
private static int TIME_OUT = 10;
private Retrofit mRetrofit = null;
public static HttpsUtils getInstance() {
if (sHttpsUtils == null) {
synchronized (HttpsUtils.class) {
if (sHttpsUtils == null) {
sHttpsUtils = new HttpsUtils();
}
}
}
return sHttpsUtils;
}
public HttpsUtils() {
initOkhttp();
getRetrofit();
}
private void initOkhttp() {
mClient = new OkHttpClient.Builder()
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.writeTimeout(TIME_OUT, TimeUnit.SECONDS)
.build();
}
private Retrofit getRetrofit() {
if (mRetrofit == null) {
mRetrofit = new Retrofit.Builder()
.client(mClient)
.baseUrl(HttpURLS.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
return mRetrofit;
}
/**
* 首先的创建
* @param tClass
* @param <T>
* @return
*/
public <T> T create(Class<T> tClass) {
return mRetrofit.create(tClass);
}
}
<strong>访问数据的接口:MainAPI</strong>
public interface MainAPI {
@GET("Android/10/{page}")
Observable<DataBeans> getAndroidAPI(@Path("page") int page);
}
- 在Model中获取数据
public class BaseMainModelImpl implements BaseMainModel {
private static String TAG = "BaseMainModelImpl";
private List<ShowDataBeans> mShowDataBeans = new ArrayList<>();
@Override
public void getAndroidAPIData(int page, final MainDataInterface dataInterface) {
HttpsUtils.getInstance()
.create(MainAPI.class)
.getAndroidAPI(page)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<DataBeans>() {
@Override
public void onNext(DataBeans beans) {
Log.i(TAG, "onNext");
if (!beans.isError()) {
mShowDataBeans.clear();
for (DataBeans.ResultsBean re : beans.getResults()) {
ShowDataBeans dataBeans=new ShowDataBeans();
dataBeans.set_id(re.get_id());
dataBeans.setImages(re.getImages());
dataBeans.setDesc(re.getDesc());
dataBeans.setSource(re.getSource());
mShowDataBeans.add(dataBeans);
}
}
dataInterface.success(mShowDataBeans);
}
@Override
public void onComplete() {
Log.i(TAG, "onComplete");
dataInterface.complete();
}
@Override
public void onError(Throwable e) {
Log.i(TAG, "onError");
dataInterface.error(e.getMessage());
}
@Override
public void onSubscribe(Disposable d) {
}
});
}
}
第四部分:也就是View和Model的桥梁部分(ViewModel)
ViewModel起的主要作用是协调View和Model的数据绑定。
注意:此处的MainViewModelImpl继承与BaseVM,BaseVM在前面部分介绍过,使用attachView和detachView,getIView的作用,传入的泛型为MainActivity,那么在获取数据完成之后或者是报错的时候直接使用getIView().complete()/error()的方式传递给activity,然后再activity中显示提示给用户,从而达到view-viewmodel -model的解耦。
public class MainViewModelImpl extends BaseVM<MainActivity> implements BaseMainVM, MainDataInterface {
private BaseMainModelImpl mMainModel;
private MainDataAdapter mDataAdapter;
private int page=1;
public MainViewModelImpl() {
mMainModel=new BaseMainModelImpl();
}
public void setDataAdapter(MainDataAdapter adapter){
mDataAdapter=adapter;
}
@Override
public void refresh() {
page=1;
mMainModel.getAndroidAPIData(1,this);
}
@Override
public void loadMore() {
page++;
mMainModel.getAndroidAPIData(page,this);
}
@Override
public void success(List<ShowDataBeans> beansList) {
if (page==1){
mDataAdapter.onRefreshData(beansList);
}else {
mDataAdapter.onLoadMoreData(beansList);
}
}
@Override
public void error(String message) {
getIView().showError(message);
}
@Override
public void complete() {
getIView().complete();
}
}
源码请见:
欢迎大家关注我的微信公众号: