Android MVP架构(RxJava+SQLBrite+Retrofit+OkHttp+Glide)
2017-05-17 18:54
525 查看
每年都有新的框架出来,比起以往的开发方式更高效,更简洁,唯一成本是学习与踩坑。框架能实现的东西,android原生知识点也是能实现的,但是前者更效率高,节省时间。使用框架,花费更少的时间,更少的力去实现一个需求,何乐而不为?
若是 , 不熟悉MVP项目架构,可以阅读Android MVP。
这里使用MVP项目架构外,还是用以下的框架:
Rxjava和RxAndroid :异步响应式框架,替代异步线程
SQLBrite :配合RxJava,替代ContentProvider+SQLIte+CursorLoader
Retrofit和OkHttp : 新的网络传输,替代HttpUrlConnection
Glide :加载图片,视频,GIF,可以替代传统的加载图像资源方式
因此,这里介绍Android MVP架构(RxJava+SQLBrite+Retrofit+OkHttp+Glide)来实现项目需求。
![](http://img.blog.csdn.net/20170514200840555?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGV4aW5nZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
一个切换界面的抽屉菜单:
![](http://img.blog.csdn.net/20170514200933704?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGV4aW5nZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
一个收藏列表的界面:
![](http://img.blog.csdn.net/20170514201023455?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGV4aW5nZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
根据上面的页面,归纳出以下功能点:
电影列表
选择多部电影进行收藏。
查看被收藏的电影列表。
按模块划分,可以分为电影列表模块,电影收藏模块。
![](http://img.blog.csdn.net/20170517184857767?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGV4aW5nZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
项目中通用的BaseView接口,具备绑定Presenter的行为:
数据库中常量字段:
SQLite数据库配置:
使用SQLBrite框架,操作数据库:不熟悉这个框架,可以先阅读SQLBrite框架。
抽象出一个增,删,查,改的行为的接口,在实现类中编写SQLBrite操作:
2.2 远程数据源模块编写:
注意点:远程数据可以分为文本数据和图像数据。
文本数据用Retrofit+OkHttp实现请求的响应:
采用OkHttp作为Retrofit的传输层 , 需 引入
标注点:采用豆瓣API中搜索功能,这里搜索张艺谋的电影,返回前20个。
先编写Retrofit中请求中发送的Body和Header,Respose的接口 :需要添加
配置Retrofit: 添加OkHttp作为传输层,RxJava适配器,Gson解析的转换器。
接下来,编写图像的配置:
图像数据用Glide实现加载:
创建ImageLoader类,封装调用方法,URI的自定义拼接,实现按图像尺寸来获取最原始数据:来源:参考于Google IO APP中处理方式。
View告诉Presenter要加载数据,Presenter要获取远程数据源,然后回调的响应数据更新到UI上.
View告诉Presenter要收藏的电影,Presenter将收藏数据传递给本地数据源,进行存储,最后Presenter将存储结果更新到UI上。
根据以上的View与Presenter交互,在一个合同接口中抽象出具体行为的View和Presenter。
在Fragment中实现View接口,实现具体操作:
在一个Pesenter接口的实现类中:使用RxJava和RxAndroid实现异步操作数据,和UI更新。
最后Activity中,创建Presenter和View:
电影收藏的业务也是类似,只要要抽出View与Presenter的交互行为,剩下的便是调用数据源。
最好,可以结合Android MVP架构来加深理解。
UI自定义类:SwipeRefreshLayout 支持非直接子类滚动视图
![](http://img.blog.csdn.net/20170514214716533?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGV4aW5nZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
Android MVP架构:http://blog.csdn.net/hexingen/article/details/72058057
Android MVP架构(Volley+CursorLoader+ContentProvider): http://blog.csdn.net/hexingen/article/details/72082419
若是 , 不熟悉MVP项目架构,可以阅读Android MVP。
这里使用MVP项目架构外,还是用以下的框架:
Rxjava和RxAndroid :异步响应式框架,替代异步线程
SQLBrite :配合RxJava,替代ContentProvider+SQLIte+CursorLoader
Retrofit和OkHttp : 新的网络传输,替代HttpUrlConnection
Glide :加载图片,视频,GIF,可以替代传统的加载图像资源方式
因此,这里介绍Android MVP架构(RxJava+SQLBrite+Retrofit+OkHttp+Glide)来实现项目需求。
前期准备:
项目中,在Gradle中引入框架的配置如下:dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:4.12' //android原生库 compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support:recyclerview-v7:25.3.1' compile 'com.android.support:design:25.3.1' //以下引入都是是采用的第三方库 //异步加载图像 compile 'com.github.bumptech.glide:glide:3.8.0' //异步线程操作 compile 'io.reactivex:rxjava:1.3.0' //UI线程操作 compile 'io.reactivex:rxandroid:1.2.1' //数据库操作,但不是ORM compile 'com.squareup.sqlbrite:sqlbrite:1.1.1' //网络请求操作 compile 'com.squareup.retrofit2:retrofit:2.3.0' compile 'com.squareup.okhttp3:okhttp:3.8.0' //解析数据,Gson方式json compile 'com.squareup.retrofit2:converter-gson:2.3.0' //网络日志输出 compile 'com.squareup.okhttp3:logging-interceptor:3.8.0' //结合RxJava使用 compile 'com.squareup.retrofit2:adapter-rxjava:2.3.0' }
接下来,解下项目需求:
一个电影列表界面:一个切换界面的抽屉菜单:
一个收藏列表的界面:
根据上面的页面,归纳出以下功能点:
电影列表
选择多部电影进行收藏。
查看被收藏的电影列表。
按模块划分,可以分为电影列表模块,电影收藏模块。
根据功能点,开始编写项目代码:
项目按(多个)业务模块,工具包模块,数据模块,UI模块分层如下:1. 项目通用的BasePrester和BaseView接口:
项目中通用的BasePresenter接口,具备订阅和取消订阅的行为:public interface BasePresenter { /** * 订阅 */ void subscribe(); /** * 取消订阅 */ void unsubscribe(); }
项目中通用的BaseView接口,具备绑定Presenter的行为:
public interface BaseView<T> { void setPresenter(T presenter); }
2. 开始编写Model中本地数据源和远程数据源:
2.1 本地数据源模块编写:数据库中常量字段:
public class MovieConstract implements BaseColumns { /** * 数据库的信息 */ public static final String SQLITE_NAME="movie.db"; public static final int SQLITE_VERSON=1; /** * 表和字段信息 */ public static final String TABLE_NAME_MOVI="movieData"; public static final String COLUMN_ID ="id"; public static final String COLUMN_YEAR="year"; public static final String COLUMN_TITLE="title"; public static final String COLUMN_IMAGES="image"; public static final String SQL_QUERY_MOVIE="select * from "+TABLE_NAME_MOVI; }
SQLite数据库配置:
public class MovieDataHelper extends SQLiteOpenHelper { public static final String CREATE_TABLE_MOVIE = "create table " + MovieConstract.TABLE_NAME_MOVI + "(" + MovieConstract._ID + " integer primary key autoincrement," + MovieConstract.COLUMN_ID + " text," + MovieConstract.COLUMN_TITLE + " text," + MovieConstract.COLUMN_YEAR + " text," + MovieConstract.COLUMN_IMAGES + " text" + ")"; public MovieDataHelper(Context context) { super(context, MovieConstract.SQLITE_NAME, null, MovieConstract.SQLITE_VERSON); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_TABLE_MOVIE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
使用SQLBrite框架,操作数据库:不熟悉这个框架,可以先阅读SQLBrite框架。
抽象出一个增,删,查,改的行为的接口,在实现类中编写SQLBrite操作:
public class MovieLocalSource implements LocalDataSource<MovieData> { private BriteDatabase briteDatabase; private static MovieLocalSource instance; private Func1<Cursor,MovieData> cursorListFunc1; private MovieLocalSource(Context context , SchedulerProvider schedulerProvider){ MovieDataHelper helper=new MovieDataHelper(context); //配置SQLBrite框架 SqlBrite sqlBrite=new SqlBrite.Builder().build(); this.briteDatabase= sqlBrite.wrapDatabaseHelper(helper,schedulerProvider.io()); //查询的数据返回Cusrsor将在这里被回调。 this.cursorListFunc1=new Func1<Cursor, MovieData>() { @Override public MovieData call(Cursor cursor) { return TransformUtils.transformMovieData(cursor); } }; } /** * 获取实例 * @param context * @param schedulerProvider * @return */ public static MovieLocalSource getInstance(Context context , SchedulerProvider schedulerProvider){ if(instance==null){ instance=new MovieLocalSource(context,schedulerProvider); } return instance; } /** * 销毁对象 */ public static void destroyInstance(){ instance=null; } /** * *返回一个Observable对象,可以结合RxJava使用 */ @Override public Observable< List<MovieData>> queryAll() { return queryAction(MovieConstract.SQL_QUERY_MOVIE,null); } @Override public Observable< List<MovieData>> queryAction(String select, String[] selectArg) { QueryObservable observable= selectArg==null?this.briteDatabase.createQuery(MovieConstract.TABLE_NAME_MOVI,select):this.briteDatabase.createQuery(MovieConstract.TABLE_NAME_MOVI,select,selectArg); return observable.mapToList(this.cursorListFunc1); } @Override public long insert(MovieData movieData) { return this.briteDatabase.insert(MovieConstract.TABLE_NAME_MOVI,TransformUtils.transformMovieData(movieData)); } /** *批量插入 */ @Override public int bulkInsert(List<MovieData> list) { int size=0; //开启事物 BriteDatabase.Transaction transaction= this.briteDatabase.newTransaction(); try { for (MovieData movieData:list){ this.briteDatabase.insert(MovieConstract.TABLE_NAME_MOVI,TransformUtils.transformMovieData(movieData)); } transaction.markSuccessful(); size=list.size(); }catch (Exception e){ size=0; e.printStackTrace(); }finally { transaction.end(); } return size; } //项目中没有删,改操作,未编写相关代码 @Override public int update(MovieData movieData, String select, String[] selectArg) { return 0; } @Override public int delite(MovieData movieData, String select, String[] selectArg) { return 0; } @Override public void deliteAll() { } }
2.2 远程数据源模块编写:
注意点:远程数据可以分为文本数据和图像数据。
文本数据用Retrofit+OkHttp实现请求的响应:
采用OkHttp作为Retrofit的传输层 , 需 引入
OkHttp库和
OkHttp:logging-interceptor库:
public class OkHttpProvider { /** * 自定义配置OkHttpClient * @return */ public static OkHttpClient createOkHttpClient(){ OkHttpClient.Builder builder=new OkHttpClient.Builder(); HttpLoggingInterceptor loggingInterceptor=new HttpLoggingInterceptor(); //打印一次请求的全部信息 loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); builder.addInterceptor(loggingInterceptor); return builder.build(); } }
标注点:采用豆瓣API中搜索功能,这里搜索张艺谋的电影,返回前20个。
https://api.douban.com/v2/movie/search?q=张艺谋
先编写Retrofit中请求中发送的Body和Header,Respose的接口 :需要添加
retrofit:adapter-rxjava库,实现适配器功能。
public interface DouBanService{ //这里返回一个Observable,用于RxJava结合使用 @GET("{path}") Observable<MovieList> movieList(@Path("path") String path , @QueryMap Map<String,String> options); }
配置Retrofit: 添加OkHttp作为传输层,RxJava适配器,Gson解析的转换器。
public class RemoteDataSource { private final String BASE_URL = "https://api.douban.com/v2/movie/"; private final Retrofit retrofit; private static RemoteDataSource instance; private DouBanService douBanService; private SchedulerProvider provider; private RemoteDataSource() { this.provider= SchedulerProvider.getInstacne(); OkHttpClient okHttpClient = OkHttpProvider.createOkHttpClient(); this.retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient)//传输层 .addConverterFactory(GsonConverterFactory.create()) //Gson解析 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) //Rxjava适配器 .build(); this.douBanService = this.retrofit.create(DouBanService.class); } /** * 单例类对象 * * @return */ public static synchronized RemoteDataSource getInstance() { if (instance == null) { instance = new RemoteDataSource(); } return instance; } /* * 在Presenter调用,在subscriber订阅者中响应 */ public Subscription movieList(Subscriber<List<Movie>> subscriber) { String url = "search"; Map<String,String> map=new HashMap<>(); map.put("q","张艺谋"); Observable<MovieList> observable = this.douBanService.movieList(url, map); //floatMap操作符转换 Observable<List<Movie>> observable1= observable.flatMap(new Func1<MovieList, Observable<List<Movie>>>() { @Override public Observable<List<Movie>> call(MovieList movieList) { return Observable.just(movieList.getSubjects()); } }); return observable1.subscribeOn(provider.io()).unsubscribeOn(provider.io()).observeOn(provider.ui()).subscribe(subscriber); } }
接下来,编写图像的配置:
图像数据用Glide实现加载:
创建ImageLoader类,封装调用方法,URI的自定义拼接,实现按图像尺寸来获取最原始数据:来源:参考于Google IO APP中处理方式。
public class ImageLoader { private final CenterCrop mCenterCrop; private final BitmapTypeRequest<String> glideModelRequest; private static final ModelCache<String ,GlideUrl> urlCache=new ModelCache<>(150); private int mPlaceHolderResId=-1; public ImageLoader(Context context){ /** * 转换网址的操作类 */ CustomVariableWithImageLoader variableWithImageLoader=new CustomVariableWithImageLoader(context); /* * 总是将资源加载成一个Bitmap */ this.glideModelRequest= Glide.with(context).using(variableWithImageLoader).from(String.class).asBitmap(); BitmapPool bitmapPool=Glide.get(context).getBitmapPool(); this.mCenterCrop=new CenterCrop(bitmapPool); } /** * 设置一个默认的 placehodler drawable * @param context * @param placeholdrResId */ public ImageLoader(Context context,int placeholdrResId){ this(context); this.mPlaceHolderResId=placeholdrResId; } public void loadImage(String url, ImageView imageView){ loadImage(url,imageView,null); } public void loadImage(String url, ImageView imageView, RequestListener<String,Bitmap> requestListener){ loadImage(url,imageView,requestListener,null,false); } public void loadImage(String url, ImageView imageView, RequestListener<String,Bitmap> requestListener, Drawable placeholderOverride, boolean crop){ BitmapRequestBuilder request=beginImageLoad(url,requestListener,crop); if(placeholderOverride!=null){ request.placeholder(placeholderOverride); }else if(mPlaceHolderResId!=-1){ request.placeholder(mPlaceHolderResId); } request.into(imageView); } public BitmapRequestBuilder beginImageLoad(String url, RequestListener<String,Bitmap> requestListener,boolean crop){ return crop==true?this.glideModelRequest.load(url).listener(requestListener).transform(this.mCenterCrop):this.glideModelRequest.load(url).listener(requestListener); } /** * 加载Resouces下图片资源. * @param context * @param drawableResId * @param imageView */ public void loadImage(Context context, int drawableResId, ImageView imageView){ Glide.with(context).load(drawableResId).into(imageView); } private static class CustomVariableWithImageLoader extends BaseGlideUrlLoader<String>{ /** * 解析格式 */ private static final Pattern PATTERN = Pattern.compile("__w-((?:-?\\d+)+)__"); public CustomVariableWithImageLoader(Context context) { super(context,urlCache); } @Override protected String getUrl(String model, int width, int height) { Matcher matcher=PATTERN.matcher(model); int bestBucket=0; if(matcher.find()){ String[] found=matcher.group(1).split("-"); for (String bucketStr : found) { bestBucket = Integer.parseInt(bucketStr); if (bestBucket >= width) { // the best bucket is the first immediately bigger than the requested width break; } } if (bestBucket > 0) { model =matcher.replaceFirst("w"+bestBucket); } } return model; } } }
3. 根据模块业务编写View和Presenter及它的实现类:
这里,列举:电影列表界面的模块View告诉Presenter要加载数据,Presenter要获取远程数据源,然后回调的响应数据更新到UI上.
View告诉Presenter要收藏的电影,Presenter将收藏数据传递给本地数据源,进行存储,最后Presenter将存储结果更新到UI上。
根据以上的View与Presenter交互,在一个合同接口中抽象出具体行为的View和Presenter。
public interface MovieListConstract { interface Presenter extends BasePresenter { /** * 收藏的数据 */ void collectionMovie(List<Movie> list); } interface View extends BaseView<Presenter> { /** * 加载从数据源中获取的数据 */ void loadMovieList(List<Movie> list); //最新结果响应在UI上 void showToast(String s); } }
在Fragment中实现View接口,实现具体操作:
public class MovieListFragment extends Fragment implements MovieListConstract.View, View.OnClickListener, SwipeRefreshLayout.OnRefreshListener { private View rootView; private RecyclerView recyclerView; private MovieListAdapter adapter; private ScrollChildSwipeRefreshLayout swipeRefreshLayout; public static final String TAG = MovieListFragment.class.getSimpleName(); public static MovieListFragment newInstance() { return new MovieListFragment(); } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { this.rootView = inflater.inflate(R.layout.fragment_movielist, container, false); return this.rootView; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // .......省略部分代码 this.presenter.subscribe(); } // .......省略部分代码 @Override public void onPause() { super.onPause(); //解除订阅 this.presenter.unsubscribe(); } private MovieListConstract.Presenter presenter; @Override public void setPresenter(MovieListConstract.Presenter presenter) { this.presenter = presenter; } @Override public void showToast(String s) { Toast.makeText(BaseApplication.getAppContext(), s, Toast.LENGTH_SHORT).show(); } @Override public void loadMovieList(List<Movie> list) { this.adapter.upData(list); this.setLoadingIndicator(false); } @Override public void onClick(View v) { if (this.adapter.getMoviesCollecion().size() == 0) { showToast("请勾选中电影"); } else { this.presenter.collectionMovie(this.adapter.getMoviesCollecion()); } } }
在一个Pesenter接口的实现类中:使用RxJava和RxAndroid实现异步操作数据,和UI更新。
public class MovieListPresenter implements MovieListConstract.Presenter { private CompositeSubscription compositeSubscription; private MovieListConstract.View view; private LocalDataSource<MovieData> dataLocalDataSource; private RemoteDataSource remoteDataSource; private SchedulerProvider schedulerProvider; public MovieListPresenter(MovieListConstract.View view, LocalDataSource<MovieData> dataLocalDataSource, RemoteDataSource remoteDataSource) { this.compositeSubscription = new CompositeSubscription(); this.dataLocalDataSource = dataLocalDataSource; this.remoteDataSource = remoteDataSource; this.schedulerProvider = SchedulerProvider.getInstacne(); this.view = view; this.view.setPresenter(this); } @Override public void collectionMovie(final List<Movie> list) { Subscription subscription = Observable.create(new Observable.OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> subscriber) { List<MovieData> movieDataList = new ArrayList<>(); for (Movie movie : list) { movieDataList.add(TransformUtils.transformMovies(movie)); } int size = dataLocalDataSource.bulkInsert(movieDataList); subscriber.onNext(size > 0 ? true : false); subscriber.onCompleted(); } }).subscribeOn(schedulerProvider.io()). observeOn(schedulerProvider.ui()). subscribe(new Observer<Boolean>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { if (isViewBind()) { String msg="收藏失败"; view.showToast(msg); } } @Override public void onNext(Boolean aBoolean) { if (isViewBind()) { String msg=aBoolean==false?"收藏失败":"收藏成功,可在收藏页面查看"; view.showToast(msg); } } }); this.compositeSubscription.add(subscription); } @Override public void subscribe() { loadRemoteTask(); } /** * 开始加载远程的数据 */ private void loadRemoteTask() { Subscription subscription = remoteDataSource.movieList(new Subscriber<List<Movie>>() { @Override public void onCompleted() { if (isViewBind()) { view.showToast("获取列表成功"); } } @Override public void onError(Throwable e) { if (isViewBind()) { view.showToast("加载失败"); } } @Override public void onNext(List<Movie> list) { view.loadMovieList(list); } }); this.compositeSubscription.add(subscription); } @Override public void unsubscribe() { this.compositeSubscription.clear(); } /** * 检查View是否被绑定 * * @return */ private boolean isViewBind() { return this.view == null ? false : true; } }
最后Activity中,创建Presenter和View:
private MovieListConstract.Presenter presenter; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_movielist); ..... MovieListFragment fragment=null; if(savedInstanceState!=null){ fragment=(MovieListFragment) getSupportFragmentManager().findFragmentByTag(MovieListFragment.TAG); }else{ fragment=MovieListFragment.newInstance(); getSupportFragmentManager().beginTransaction().add(R.id.movielist_content_layout,fragment,MovieListFragment.TAG).commit(); } this.presenter=new MovieListPresenter(fragment,MovieLocalSource.getInstance(BaseApplication.getAppContext(), SchedulerProvider.getInstacne()), RemoteDataSource.getInstance()); }
电影收藏的业务也是类似,只要要抽出View与Presenter的交互行为,剩下的便是调用数据源。
最好,可以结合Android MVP架构来加深理解。
4. 一些工具类,和UI类配置:
转换工具类:转换常用对象public class TransformUtils { /** * 将Cursor 生成MovieData对象 * @param cursor * @return */ public static MovieData transformMovieData(Cursor cursor) { MovieData movieData = new MovieData(); movieData.setId(cursor.getString(cursor.getColumnIndex(MovieConstract.COLUMN_ID))); movieData.setTitle(cursor.getString(cursor.getColumnIndex(MovieConstract.COLUMN_TITLE))); movieData.setYear(cursor.getString(cursor.getColumnIndex(MovieConstract.COLUMN_YEAR))); movieData.setImages(cursor.getString(cursor.getColumnIndex(MovieConstract.COLUMN_IMAGES))); return movieData; } public static MovieData transformMovies(Movie movie){ MovieData movieData=new MovieData(); movieData.setId(movie.getId()); movieData.setYear(movie.getYear()); movieData.setTitle(movie.getTitle()); movieData.setImages(movie.getImages().getLarge()); return movieData; } /** * 将Movie生成Cursor. * @param movie * @return */ public static ContentValues transformMovieData(MovieData movie){ ContentValues contentValues=new ContentValues(); contentValues.put(MovieConstract.COLUMN_ID,movie.getId()); contentValues.put(MovieConstract.COLUMN_TITLE,movie.getTitle()); contentValues.put(MovieConstract.COLUMN_YEAR,movie.getYear()); contentValues.put(MovieConstract.COLUMN_IMAGES,movie.getImages()); return contentValues; } /** * 将Movie生成Cursor. * @param movie * @return */ public static ContentValues transformMovie(Movie movie){ ContentValues contentValues=new ContentValues(); contentValues.put(MovieConstract.COLUMN_ID,movie.getId()); contentValues.put(MovieConstract.COLUMN_TITLE,movie.getTitle()); contentValues.put(MovieConstract.COLUMN_YEAR,movie.getYear()); contentValues.put(MovieConstract.COLUMN_IMAGES,movie.getImages().getLarge()); return contentValues; } }
UI自定义类:SwipeRefreshLayout 支持非直接子类滚动视图
ublic class ScrollChildSwipeRefreshLayout extends SwipeRefreshLayout { private View scrollUpChild; public ScrollChildSwipeRefreshLayout(Context context, AttributeSet attrs) { super(context, attrs); } /** * 设置在哪个view中触发刷新。 * @param view */ public void setScrollUpChild(View view){ this.scrollUpChild=view; } /** *ViewCompat..canScrollVertically():用于检查view是否可以在某个方向上垂直滑动 * @return */ @Override public boolean canChildScrollUp() { if(scrollUpChild!=null){ return ViewCompat.canScrollVertically(scrollUpChild,-1); } return super.canChildScrollUp(); } }
5. 项目运行效果如下:
本项目的代码: https://github.com/13767004362/MVPPractice
资源参考:Android MVP架构:http://blog.csdn.net/hexingen/article/details/72058057
Android MVP架构(Volley+CursorLoader+ContentProvider): http://blog.csdn.net/hexingen/article/details/72082419
相关文章推荐
- Android Material Design + MVP + Rxjava + Retrofit + Okhttp + Glide一个小项目
- android架构篇mvp+rxjava+retrofit+eventBus
- Kotlin Android Extensions+Android MVP项目(RxJava+Rerotfit+OkHttp+Glide)
- [android架构篇]mvp+rxjava+retrofit+eventBus
- Kotlin Anko Layout+MVP(Glide,Retrofit,OkHttp,RxJava)开发Android运用程序
- Android项目MVP模式框架+okhttp+rxjava+retrofit网络框架
- MVP架构下Android的Rxjava与Retrofit 结合
- Android应用架构之Retrofit使用 RxJava 详解 jsoup Android 平滑图片加载和缓存库 Glide 使用详解
- [android架构篇]mvp+rxjava+retrofit+eventBus
- Android中的Retrofit+OkHttp+RxJava缓存架构使用
- MVP+Retrofit+RxJava+Okhttp构造一个合格的Android框架
- Android——MVP+xRecyclerView+Retrofit+OkHttp+RxJava结合应用
- LookLook剖析,架构概述——MVP、Retrofit+RxJava
- [置顶] MVP架构-Android官方MVP项目和响应式MVP-RxJava项目架构分析对比解读
- MVP架构-Android官方MVP项目和响应式MVP-RxJava项目架构分析对比解读
- Android应用架构之Retrofit和RxJava使用
- Android MVP+Retrofit+RxJava实践小结
- Android MVP+Retrofit+RxJava实践小结
- Android三大设计模式之一------------------MVP设计模式(包括rxjava+retrofit网络请求框架)
- Rxjava + retrofit + dagger2 + mvp搭建Android框架