MVVM简单例子
来源:互联网 发布:鉴知往来的意思 编辑:程序博客网 时间:2024/06/05 04:02
概述
说到Android MVVM,相信大家都会想到Google 2015年推出的DataBinding框架。然而两者的概念是不一样的,不能混为一谈。MVVM是一种架构模式,而DataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个工具。
之前看过很多关于Android MVVM的博客,但大多数提到的都是DataBinding的基本用法,很少有文章仔细讲解在Android中是如何通过DataBinding去构建MVVM的应用框架的。View、ViewModel、Model每一层的职责如何?它们之间联系怎样、分工如何、代码应该如何设计?这是我写这篇文章的初衷。
接下来,我们先来看看什么是MVVM,然后再一步一步来设计整个MVVM框架。
MVC、MVP、MVVM
首先,我们先大致了解下Android开发中常见的模式。
MVC
- View:XML布局文件。
- Model:实体模型(数据的获取、存储、数据状态变化)。
- Controller:对应于Activity,处理数据、业务和UI。
从上面这个结构来看,Android本身的设计还是符合MVC架构的,但是Android中纯粹作为View的XML视图功能太弱,我们大量处理View的逻辑只能写在Activity中,这样Activity就充当了View和Controller两个角色,直接导致Activity中的代码大爆炸。相信大多数Android开发者都遇到过一个Acitivty数以千行的代码情况吧!所以,更贴切的说法是,这个MVC结构最终其实只是一个Model-View(Activity:View&Controller)的结构。
MVP
- View: 对应于Activity和XML,负责View的绘制以及与用户的交互。
- Model: 依然是实体模型。
- Presenter:负责完成View与Model间的交互和业务逻辑。
前面我们说,Activity充当了View和Controller两个角色,MVP就能很好地解决这个问题,其核心理念是通过一个抽象的View接口(不是真正的View层)将Presenter与真正的View层进行解耦。Persenter持有该View接口,对该接口进行操作,而不是直接操作View层。这样就可以把视图操作和业务逻辑解耦,从而让Activity成为真正的View层。
但MVP也存在一些弊端:
- Presenter(以下简称P)层与View(以下简称V)层是通过接口进行交互的,接口粒度不好控制。粒度太小,就会存在大量接口的情况,使代码太过碎版化;粒度太大,解耦效果不好。同时对于UI的输入和数据的变化,需要手动调用V层或者P层相关的接口,相对来说缺乏自动性、监听性。如果数据的变化能自动响应到UI、UI的输入能自动更新到数据,那该多好!
- MVP是以UI为驱动的模型,更新UI都需要保证能获取到控件的引用,同时更新UI的时候要考虑当前是否是UI线程,也要考虑Activity的生命周期(是否已经销毁等)。
- MVP是以UI和事件为驱动的传统模型,数据都是被动地通过UI控件做展示,但是由于数据的时变性,我们更希望数据能转被动为主动,希望数据能更有活性,由数据来驱动UI。
- V层与P层还是有一定的耦合度。一旦V层某个UI元素更改,那么对应的接口就必须得改,数据如何映射到UI上、事件监听接口这些都需要转变,牵一发而动全身。如果这一层也能解耦就更好了。
- 复杂的业务同时也可能会导致P层太大,代码臃肿的问题依然不能解决。
MVVM
- View: 对应于Activity和XML,负责View的绘制以及与用户交互。
- Model: 实体模型。
- ViewModel:
负责完成View与Model间的交互,负责业务逻辑。
MVVM的目标和思想与MVP类似,利用数据绑定(Data Binding)、依赖属性(Dependency Property)、命令(Command)、路由事件(Routed Event)等新特性,打造了一个更加灵活高效的架构。
1. 导包
compile 'com.android.support:appcompat-v7:25.0.1' compile 'com.android.support:design:25.0.1' compile 'com.android.support:cardview-v7:25.0.1' compile 'io.reactivex:rxjava:1.1.0' compile 'io.reactivex:rxandroid:1.1.0' compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4' compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4' compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4' compile 'com.github.bumptech.glide:glide:3.7.0'
2. Model层
1.网络帮助类----- public class RetrofitHelper { private static final int DEFAULT_TIMEOUT = 10; private Retrofit retrofit; private HttpMovieService movieService; OkHttpClient.Builder builder; /** * 获取RetrofitHelper对象的单例 * */ private static class Singleton { private static final RetrofitHelper INSTANCE = new RetrofitHelper(); } public static RetrofitHelper getInstance() { return Singleton.INSTANCE; } private RetrofitHelper() { builder = new OkHttpClient.Builder(); builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS); retrofit = new Retrofit.Builder() .client(builder.build()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .baseUrl(HttpMovieService.BASE_URL) .build(); movieService = retrofit.create(HttpMovieService.class); } public void getMovies(Subscriber<Response> subscriber, int start, int count) { movieService.getMovies(start, count) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(subscriber); }}//网络接口public interface HttpMovieService { String BASE_URL = "https://api.douban.com/v2/movie/"; @GET("top250") Observable<Response<Movie>> getMovies(@Query("start") int start, @Query("count") int count);}2.实体对象------ public class Movie { public String id; public String alt; public String year; public String title; public String original_title; public List<String> genres; public List<Cast> casts; public List<Cast> directors; public Avatars images; public Rating rating; public static class Rating { public float average; } public static class Cast{ public String id; public String name; public String alt; public Avatars avatars; } public static class Avatars{ public String small; public String medium; public String large; }}/** * Created by zlc on 2017/10/22. */public class BaseInfo<T> { public List<T> subjects; public int count; public int start; public int total;}
3. View层
1.fragment设计public class MovieFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener,CompletedListener { private MovieFragmentBinding movieFragmentBinding; private MovieAdapter movieAdapter; private MainViewModel mainViewModel; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { movieFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.movie_fragment, container, false); return movieFragmentBinding.getRoot(); } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); initData(); } private void initData() { movieAdapter = new MovieAdapter(getActivity()); movieFragmentBinding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); movieFragmentBinding.recyclerView.setItemAnimator(new DefaultItemAnimator()); movieFragmentBinding.recyclerView.setAdapter(movieAdapter); mainViewModel = new MainViewModel(movieAdapter,this); movieFragmentBinding.setViewModel(mainViewModel); movieFragmentBinding.swipeRefreshLayout.setOnRefreshListener(this); } public static Fragment getInstance() { return new MovieFragment(); } @Override public void onRefresh() { mainViewModel.refreshData(); } @Override public void onCompleted() { if(movieFragmentBinding.swipeRefreshLayout.isRefreshing()){ movieFragmentBinding.swipeRefreshLayout.setRefreshing(false); } }}2. RecycleView适配器编写/** * Created by Administrator on 2017/10/22. */public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.MovieViewHolder>{ private List<Movie> mDatas; private Context mContext; public MovieAdapter(Context context){ mDatas = new ArrayList<>(); this.mContext = context; } @Override public MovieAdapter.MovieViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { MovieItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.movie_item, parent, false); MovieViewHolder viewHolder = new MovieViewHolder(binding.getRoot()); viewHolder.setBinding(binding); return viewHolder; } @Override public void onBindViewHolder(MovieAdapter.MovieViewHolder holder, int position) { Movie movie = mDatas.get(position); MovieViewModel movieViewModel = new MovieViewModel(movie); holder.getBinding().setViewModel(movieViewModel); } @Override public int getItemCount() { return mDatas.size(); } public void setMovies(List<Movie> movies) { mDatas = movies; notifyDataSetChanged(); } public void clearAll() { mDatas.clear(); } public static class MovieViewHolder extends RecyclerView.ViewHolder{ private MovieItemBinding binding; public MovieViewHolder(View itemView) { super(itemView); } public void setBinding(MovieItemBinding binding) { this.binding = binding; } public MovieItemBinding getBinding() { return binding; } }}
4. ViewModel
1. 针对fragment编写的ViewModel/** * Created by zlc on 2017/10/22. */public class MovieFragmentViewModel { public ObservableField<Integer> contentViewVisibility; public ObservableField<Integer> progressBarVisibility; public ObservableField<Integer> errorInfoLayoutVisibility; private MovieAdapter movieAdapter; private CompletedListener completedListener; public MovieFragmentViewModel(MovieAdapter movieAdapter, CompletedListener completedListener) { this.movieAdapter = movieAdapter; this.completedListener = completedListener; initData(); getMovieInfo(); } private void getMovieInfo() { RetrofitHelper.getInstance().getMovies(new Subscriber<BaseInfo>() { @Override public void onCompleted() { Log.e("MovieFragmentViewModel", "onCompleted"); hideAll(); contentViewVisibility.set(View.VISIBLE); completedListener.onCompleted(); } @Override public void onError(Throwable e) { Log.e("MovieFragmentViewModel", "onError="+e.getMessage()); hideAll(); errorInfoLayoutVisibility.set(View.VISIBLE); completedListener.onCompleted(); } @Override public void onNext(BaseInfo response) { if(response!=null) movieAdapter.setMovies(response.subjects); } },0,20); } private void initData() { contentViewVisibility = new ObservableField<>(); progressBarVisibility = new ObservableField<>(); errorInfoLayoutVisibility = new ObservableField<>(); show(View.GONE,contentViewVisibility,errorInfoLayoutVisibility); progressBarVisibility.set(View.VISIBLE); } public void refreshData() { movieAdapter.clearAll(); getMovieInfo(); } private void hideAll(){ show(View.GONE,contentViewVisibility,errorInfoLayoutVisibility,progressBarVisibility); } private void show(int visable,ObservableField ...fields){ for (int i = 0; i < fields.length; i++) { fields[i].set(visable); } }}2. 针对适配器编写的ViewModelpublic class MovieAdapterViewModel extends BaseObservable{ private Movie movie; public MovieAdapterViewModel(Movie movie) { this.movie = movie; } public String getTitle() { return movie.title; } public float getRating() { return movie.rating.average; } public String getRatingText() { return String.valueOf(movie.rating.average); } public String getMovieType() { StringBuilder builder = new StringBuilder(); for (String s : movie.genres) { builder.append(s + " "); } return builder.toString(); } public String getYear(){ return movie.year; } public String getImageUrl() { return movie.images.small; } @BindingAdapter({"app:imageUrl"}) public static void loadImage(ImageView imageView,String url) { Glide.with(imageView.getContext()) .load(url) .placeholder(R.drawable.cover) .error(R.drawable.cover) .into(imageView); }}
5. 布局
1. fragment布局<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="com.zlc.mvvmsample.viewModel.MovieFragmentViewModel"/> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v4.widget.SwipeRefreshLayout android:visibility="@{viewModel.contentViewVisibility}" android:id="@+id/swipe_refresh_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:background="#ddd" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="8dp"> </android.support.v7.widget.RecyclerView> </android.support.v4.widget.SwipeRefreshLayout> <ProgressBar style="?android:attr/progressBarStyleLarge" android:id="@+id/progress_bar" android:visibility="@{viewModel.progressBarVisibility}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true"/> <LinearLayout android:layout_width="match_parent" android:id="@+id/error_info_layout" android:visibility="@{viewModel.errorInfoLayoutVisibility}" android:orientation="vertical" android:layout_height="match_parent"> <TextView android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=""/> </LinearLayout> </RelativeLayout></layout>2. RecycleView子条目布局<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/tools"> <data> <variable name="viewModel" type="com.zlc.mvvmsample.viewModel.MovieAdapterViewModel"/> </data> <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="wrap_content" card_view:cardCornerRadius="4dp" card_view:cardBackgroundColor="@color/background" card_view:cardUseCompatPadding="true"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:layout_margin="8dp" android:layout_width="60dp" android:layout_height="100dp" android:src="@drawable/cover" app:imageUrl="@{viewModel.imageUrl}" android:id="@+id/cover"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_margin="8dp" android:orientation="vertical"> <TextView android:textColor="@android:color/black" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.title}" android:textSize="12sp"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:orientation="horizontal"> <android.support.v7.widget.AppCompatRatingBar android:id="@+id/ratingBar" style="?android:attr/ratingBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:isIndicator="true" android:max="10" android:numStars="5" android:rating="@{viewModel.rating}" /> <TextView android:id="@+id/rating_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginLeft="6dp" android:text="@{viewModel.ratingText}" android:textColor="?android:attr/textColorSecondary" android:textSize="10sp" /> </LinearLayout> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="?android:attr/textColorSecondary" android:textSize="10sp" android:text="@{viewModel.movieType}" android:id="@+id/movie_type_text" android:layout_marginTop="6dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="?android:attr/textColorSecondary" android:textSize="10sp" android:text="@{viewModel.year}" android:id="@+id/year_text" android:layout_marginTop="6dp" /> </LinearLayout> </LinearLayout> </android.support.v7.widget.CardView></layout>
6. 联系方式
qq:1509815887@qq.com email : zlc921022@163.com phone : 18684732678
7.下载地址
点击去下载
- MVVM简单例子
- wp8使用mvvm模式简单例子
- WPF 一个MVVM的简单例子
- WPF 一个MVVM的简单例子
- wp8使用mvvm模式简单例子(二)---登陆功能,事件触发
- MVVM模式简单理解
- MVVM简单了解
- WPF MVVM 简单实现
- MVVM的简单使用
- MVVM的简单使用
- MVC,MVVM简单理解
- MVVM架构简单使用
- MVVM架构简单使用
- MVVM简单实现
- MVVM简单实践
- MVVM:MVVM架构的简单解析
- MVVM架构的简单解析
- WPF---MVVM模式简单应用
- AngularJs 入门购物车
- linux搭建MySQL主从复制,读写分离(完善篇)
- HTML基础--position 绝对定位 相对定位 锚点链接
- 密码的·分类
- 最优化基础:损失函数可视化、折页损失函数 & 梯度计算
- MVVM简单例子
- 17年10月自考--一直在路上~
- HDU 4417 Super Mario(线段树离线处理/主席树)
- C语言(一)C语言格式
- 从Xutils运行时注解复习Java注解
- 有关时间复杂度的计算
- 方法调用
- angular中的value、factory、service、constent
- 出栈顺序问题