Collection聚合了项目搭建的一些基本模块,节约开发者时间,协助项目的快速搭建,RecyclerView+Adapter+Retrofit+RxJava+MVP+DataManager+基本Base,能够满足一个项目的基本实现。
github地址:https://github.com/usernameyangyan/Collection-Android
掘金地址:https://juejin.im/post/5ab9987451882555635e5401
更新说明
v1.2.7
1.增加自定义控件TabLayout。
v1.2.6
1.RxJava的依赖更新。 2.修正RecyclerView头部布局不能铺满问题。 3.PopupWindow的使用。 4.DisplayUtils工具类对状态栏的修改。
v1.2.5
1.修正Retrofit DEFAULT_POST请求方式指向错误。
2.Retrofit 数据解析兼容没有公用been类,可以指定公用been类和不指定公用been类、或者混合使用。
3.Realm增加数据迁移(数据库字段增加或移除)。
4.增加几种通用的Dialog弹窗,提供方法自定义。
5.提供几种比较常用的Utils工具类。
v1.2.4
1.增加DataManager用来统一管理数据请求,包括Retrofit的请求、SharePreference以及Realm的数据请求。
2.Retrofit的请求的整合。 3.PullToRefreshRecyclerView的空布局bug修改。
文章目录
1.框架的引入
2.PullToRefreshRecyclerView的使用
- 框架默认下拉刷新、上拉加载更多样式
- 自定义下拉刷新、上拉加载更多样式
- 上拉加载更多结合SwipeRefreshLayout使用
- RecyclerView添加头部、空布局
- 上拉加载更多实现NoMoreData、自动刷新
3.BaseRecyclerViewAdapter的使用
- BaseRecyclerViewAdapter比原始Adapter代码量减少
- 添加Item的点击事件
- 添加Item的长按事件
- 多布局的使用
- 添加拖拽、滑动删除
4.MVP+RxJava+Retrofit的封装使用
- 框架中的Retrofit+RxJava封装的了解
- 使用框架在项目需要做的操作
- MVP+RxJava+Retrofit+OkHttp的缓存机制
- MVP+RxJava+Retrofit+自定义磁盘缓存机制
5.DataManager的使用
- DataManager的Retrofit请求
- DataManager的SharePreference的使用
- DataManager的Realm的使用
6.Base的使用
- Base封装了MVP和项目的基类
- UI状态控制StateView的使用
- 三步实现Permission(权限)设置
- 提供几种比较常用的Dialog弹框
- 提供几种比较常用的PopupWindow弹框
- 使用DisplayUtils修改状态栏
- 提供几种比较常用的Utils工具类
7.CustomView的使用
- CommonTabLayout的使用
- OutSideFrameTabLayout的使用
一、框架整体模块
二、框架的引入
implementation 'com.youngman:collectionlibrary:1.2.7' compile 'com.youngman:collectionlibrary:1.2.7'
Error:Could not find com.android.support:appcompat-v7:27.x.x. 因为library的Support Repository是27.x.x,可能跟项目有所冲突,如果sdk已经装了27还是会出现同样的错误。 解决办法:在项目根build.gradle中加入 maven { url "https://maven.google.com" }
三 、PullToRefreshRecyclerView的使用
1.框架默认下拉刷新、上拉加载更多样式
(1)布局文件
复制代码
(2)代码设置
mRecyclerView.setPullRefreshEnabled(true);
mRecyclerView.setLoadMoreEnabled(true);
复制代码
2、自定义下拉刷新、上拉加载更多样式
(1)代码设置
mRecyclerView.setPullRefreshEnabled(true);
mRecyclerView.setLoadMoreEnabled(true);
mRecyclerView.setRefreshView(new DefinitionAnimationRefreshHeaderView(getActivity()));
mRecyclerView.setLoadMoreView(new DefinitionAnimationLoadMoreView(getActivity()));
复制代码
(2)自定义刷新和加载更多样式
在进行自定义View之前先来了解刷新和加载更多的几种状态:
① BasePullToRefreshView刷新:
/***
* 下拉刷新分为4个状态
*/
//下拉的状态(还没到下拉到固定的高度时)
public static final int STATE_PULL_DOWN=0;//
//下拉到固定高度提示释放刷新的状态
public static final int STATE_RELEASE_REFRESH=1;
//刷新状态
public static final int STATE_REFRESHING=2;
//刷新完成
public static final int STATE_DONE=3;
复制代码
②BaseLoadMoreView加载更多:
/***
* 加载更多分为3个状态
*/
//正在加载
public final static int STATE_LOADING = 0;
//加载完成
public final static int STATE_COMPLETE = 1;
//没有数据
public final static int STATE_NODATA= 2;
复制代码
自定义刷新的步骤:
①自定义View继承BasePullToRefreshView,重写initView()、setRefreshTimeVisible(boolean show)、destroy()方法:
在initView()做自定义布局、相关动画的初始化,最后在initView()方法的最后面添加以下代码即可。
//mContainer =LayoutInflater.from(context).inflate(R.layout.layout_default_arrow_refresh, null);
//把刷新头部的高度初始化为0
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
lp.setMargins(0, 0, 0, 0);
this.setLayoutParams(lp);
this.setPadding(0, 0, 0, 0);
addView(mContainer, new LayoutParams(LayoutParams.MATCH_PARENT, 0));//把刷新布局添加进去
setGravity(Gravity.BOTTOM);
//测量高度
measure(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
mMeasuredHeight = getMeasuredHeight();
复制代码
setRefreshTimeVisible(boolean show)是用来设置是否显示刷新时间控件,在默认刷新样式中通过mRecyclerView.setRefreshTimeVisible(false)即可隐藏刷新时间,如果在自定义的布局中没有这项这个方法就可以忽略。
destroy()是用来关掉改页面时把刷新View的一些动画等释放,防止内存泄漏。
②实现BasePullToRefreshView.OnStateChangeListener监听(重点,主要是进行状态切换后的相关操作逻辑)
在构造函数中
onStateChangeListener=this;
复制代码
onStateChange的模板样式
@Override
public void onStateChange(int state) {
//下拉时状态相同不做继续保持原有的状态
if (state == mState) return ;
//根据状态进行动画显示
switch (state){
case STATE_PULL_DOWN://跟随手指下拉的状态
//clearAnim();
//startAnim();
break;
case STATE_RELEASE_REFRESH://下拉释放
break;
case STATE_REFRESHING://正在进行刷新
//clearAnim();
//startAnim();
scrollTo(mMeasuredHeight);//这段代码需要添加
break;
case STATE_DONE://刷新完成
break;
}
mState = state;//状态的更新
}
复制代码
自定义加载更多的步骤(包括没有更多数据显示的操作):
①自定义View继承BaseLoadMoreView,重写initView()、setState()、destroy()方法:
在initView()做自定义布局、相关动画的初始化,最后在initView()方法的最后面添加以下代码即可。
//mContainer = LayoutInflater.from(context).inflate(R.layout.layout_definition_animation_loading_more, null);
addView(mContainer);
setGravity(Gravity.CENTER);
复制代码
destroy()是用来关掉改页面时把刷新View的一些动画等释放,防止内存泄漏。
在setState()进行状态切换后的相关操作逻辑,模板样式:
@Override
public void setState(int state) {
switch (state){
case STATE_LOADING://正在加载
//loadMore_Ll.setVisibility(VISIBLE);
//noDataTv.setVisibility(INVISIBLE);
//animationDrawable= (AnimationDrawable) loadingIv.getDrawable();
//animationDrawable.start();
this.setVisibility(VISIBLE);//这段代码需要添加
break;
case STATE_COMPLETE:
//if(animationDrawable!=null){
// animationDrawable.stop();
//}
this.setVisibility(GONE);//这段代码需要添加
break;
case STATE_NODATA:
//loadMore_Ll.setVisibility(INVISIBLE);
//noDataTv.setVisibility(VISIBLE);
//animationDrawable= (AnimationDrawable) loadingIv.getDrawable();
//animationDrawable.start();
this.setVisibility(VISIBLE);//这段代码需要添加
break;
}
mState = state;//状态的更新
}
复制代码
②注意:在自定义加载更多样式时,如果需要有没有更多加载更多数据提示同样需要在布局中写好,然后在onSatae中根据状态对加载和没有跟多显示提示进行显示隐藏操作。
3、上拉加载更多配合SwipeRefreshLayout使用
(1)布局文件
复制代码
(2)代码设置
mRecyclerView.setLoadMoreEnabled(true);
mRecyclerView.setLoadMoreView(new DefinitionAnimationLoadMoreView(getActivity()));
swl_Refresh.setColorSchemeResources(R.color.colorAccent);
swl_Refresh.setOnRefreshListener(this);
复制代码
(3)注意的问题
由于PullToRefreshRecyclerView的下拉刷新和下拉加载更多完成时会自动刷新Adapter,而SwipeRefreshLayout刷新完成时需要手动进行notifyDataSetChanged刷新适配器。
4、RecyclerView添加头部、空布局
(1)代码设置
View emptyView = LayoutInflater.from(getActivity()).inflate(R.layout.layout_empty,null);
mRecyclerView.setEmptyView(emptyView);
复制代码
5、上拉加载更多实现NoMoreData、自动刷新
(1)上拉加载更多数据的布局设置在上面的自定义LoadingMoreView中有介绍,如果要显示没有更多数据提示只需要在LoadMore返回数据之后设置:
mRecyclerView.setNoMoreDate(true);
复制代码
(2)自动刷新需要列表已经填充了数据之后再做自动刷新操作才会生效:
mRecyclerView.setAutoRefresh();
复制代码
6、PullToRefreshRecyclerView的其他使用以及注意问题
(1)提供的使用方法
mRecyclerView.isLoading() //是否正在加载更多
mRecyclerView.loadMoreComplete() //加载更多完成
mRecyclerView.isRefreshing() //是否正在刷新
mRecyclerView.refreshComplete(); //刷新数据完成
复制代码
(2)刷新、加载更多接口回调,PullToRefreshRecyclerView.OnRefreshAndLoadMoreListener提供一下两个方法:
onRecyclerViewRefresh()
onRecyclerViewLoadMore()
复制代码
(3)使用实例部分代码:
//下拉刷新、上拉加载更多设置
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
refreshRv.setLayoutManager(linearLayoutManager);
refreshRv.setRefreshView(new DefinitionAnimationRefreshHeaderView(getActivity()));
refreshRv.setLoadMoreView(new DefinitionAnimationLoadMoreView(getActivity()));
refreshRv.setPullRefreshEnabled(true);
refreshRv.setLoadMoreEnabled(true);
refreshRv.setRefreshAndLoadMoreListener(this);
============================下面是下拉刷新上拉加载更多的一些操作=========================================
//刷新页面
@Override
public void refreshUI(List newsList) {
//先做数据拼接
if(newsList!=null){
if (pageSize == 1) {
mDatas.clear();
mDatas.addAll(newsList);
} else {
mDatas.addAll(newsList);
}
}
if (weChatFeaturedAdapter == null) {
//配合StateView使用,StateView具体使用下面有介绍
if(mDatas.size()==0){
stateView.showViewByState(StateView.STATE_EMPTY);
}else{
stateView.showViewByState(StateView.STATE_NO_DATA);
}
weChatFeaturedAdapter = new WeChatFeaturedAdapter(getActivity(), mDatas, refreshRv);
refreshRv.setAdapter(weChatFeaturedAdapter);
} else {
//判断该操作是下拉刷新还是上拉加载更多
if (refreshRv.isLoading()) {
refreshRv.loadMoreComplete();
//如果没有更多数据就显示没有更多数据提示
if (newsList==null||newsList.size() == 0) {
refreshRv.setNoMoreDate(true);
}
} else if (refreshRv.isRefreshing()) {
refreshRv.refreshComplete();
}
}
}
复制代码
(4)其他注意问题
①在设置RecyclerView是要设LayoutManager
②如果使用PullToRefreshRecyclerView在Activty/Fragment中的onDestroy()调用mRecyclerView.destroy()防止内存泄漏。
@Override
public void onDestroy() {
super.onDestroy();
if(mRecyclerView!=null){
mRecyclerView.destroy();
}
}
复制代码
③设置refreshRv.setLoadMoreEnabled(true),当填充的数据的列表size为0的同时还通过RecyclerView设置分割线底部就会出现一个空白的item,这个item就是加载更多显示的Item。
解决办法:不通过RecyclerView设置分割线,直接在布局自定义分割线。
四、BaseRecyclerViewAdapter的使用
1.BaseRecyclerViewAdapter的比原始Adapter的代码量减小
在BaseRecyclerViewAdapter中的BaseViewHolder进行布局转化,同时定义了一些比较基本的View操作,使用简单。
(1)使用代码:
public class PullToRecyclerViewAdapter extends BaseRecyclerViewAdapter {
public PullToRecyclerViewAdapter(Context mContext, List mDatas, PullToRefreshRecyclerView pullToRefreshRecyclerView) {
super(mContext, R.layout.item_pull_refresh, mDatas, pullToRefreshRecyclerView);
}
@Override
protected void convert(BaseViewHolder baseViewHolder, String s) {
baseViewHolder.setText(R.id.title,s);
}
}
复制代码
①使用者需要在继承BaseRecyclerViewAdapter时传入一个数据实体类型,具体的操作在convert()方法中操作。
②BaseViewHolder提供了一些常用View的基本操作,通过baseViewHolder.getView()可得到布局中的控件。
(2)BaseRecyclerViewAdapter提供了两个构造函数
public BaseRecyclerViewAdapter(Context mContext, int mLayoutResId, List mDatas, PullToRefreshRecyclerView pullToRefreshRecyclerView) {
this.mContext = mContext;
this.mLayoutResId = mLayoutResId;
this.mDatas = mDatas;
this.mRecyclerView=pullToRefreshRecyclerView;
}
public BaseRecyclerViewAdapter(Context mContext, int mLayoutResId, List mDatas) {
this.mContext = mContext;
this.mLayoutResId = mLayoutResId;
this.mDatas = mDatas;
}
复制代码
主要是对PullToRefreshRecyclerView和RecyclerView的适配,使用时适配器根据需要使用对应的构造函数。
2.添加Item的点击和长按事件
(1) Item点击事件实现
itemClickAdapter.setOnItemClickListener(new BaseRecyclerViewAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
showToast(mDatas.get(position).getTitle());
}
});
复制代码
(2)Item长按事件实现
itemClickAdapter.setOnItemLongClickListener(new BaseRecyclerViewAdapter.onItemLongClickListener() {
@Override
public boolean onItemLongClick(View view, int position) {
showToast("进行长按操作");
return true;
}
});
复制代码
(3)也可以实现BaseRecyclerViewAdapter.OnItemClickListener和BaseRecyclerViewAdapter.onItemLongClickListener
//事件监听
itemClickAdapter.setOnItemClickListener(this);
itemClickAdapter.setOnItemLongClickListener(this);
//点击实现
@Override
public void onItemClick(View view, int position) {
showToast(mDatas.get(position).getTitle());
}
@Override
public boolean onItemLongClick(View view, int position) {
showToast("进行长按操作");
return true;
}
复制代码
3.多布局的使用
BaseRecyclerViewAdapter的多布局实现需要注意的四步:
①自定义Adapter需要继承BaseRecyclerViewMultiItemAdapter。
② 数据实体类需要继承BaseMultiItemEntity,在getItemViewType()返回布局类型。
③ 在自定义Adapter中的构造函数中通过addItemType()传入不同类型对应的布局。
④在自定义Adapter中的convert进行类型判断,做相对应的操作。
public class MultipleAdapter extends BaseRecyclerViewMultiItemAdapter {
private int mHeight;
public MultipleAdapter(Context mContext, List mDatas) {
super(mContext, mDatas);
mHeight = DisplayUtil.dip2px(mContext, 100);
addItemType(MultiItem.TYPE_TEXT, R.layout.item_main);
addItemType(MultiItem.TYPE_IMG, R.layout.item_img);
addItemType(MultiItem.TYPE_TEXT_IMG, R.layout.item_click);
}
@Override
protected void convert(BaseViewHolder baseViewHolder, MultiItem multiItem) {
switch (baseViewHolder.getItemViewType()) {
case MultiItem.TYPE_TEXT:
baseViewHolder.getView(R.id.card_view).getLayoutParams().height = mHeight;
baseViewHolder.setText(R.id.title, multiItem.getTitle());
break;
case MultiItem.TYPE_IMG:
baseViewHolder.setImageResource(R.id.ivImg, multiItem.getRes());
break;
case MultiItem.TYPE_TEXT_IMG:
baseViewHolder.setImageResource(R.id.ivImg, multiItem.getRes());
baseViewHolder.setText(R.id.titleTv, multiItem.getTitle());
break;
}
}
复制代码
4.添加拖拽、滑动删除
局限:只针对RecyclerView,对本框架封装的PullToRefreshRecyclerView会出现混乱。
①BaseRecyclerViewAdapter和BaseRecyclerViewMultiItemAdapter都已经封装支持拖拽、滑动,适配器只需要根据需求继承其中一个即可。
②框架提供了一个BaseRecycleItemTouchHelper,对于普通的左右滑动删除、拖拽已经实现,如果想自定义可以继承BaseRecycleItemTouchHelper类,再重写相对应的方法进行实现。
④在Activity/Fragment中需要实现以下代码:
ItemTouchHelper.Callback callback=new BaseRecycleItemTouchHelper(dragAndDeleteAdapter);
ItemTouchHelper itemTouchHelper=new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);
复制代码
⑤BaseRecyclerViewAdapter.OnDragAndDeleteListener进行操作动作完成之后的回调。
@Override
public void onDragAndDeleteFinished() {
mRecyclerView.postDelayed(new Runnable() {
@Override
public void run() {
dragAndDeleteAdapter.notifyDataSetChanged();
showToast("操作完成");
}
},300);
}
复制代码
注意:需要延时再进行逻辑操作,不然会出现数据混乱。
四、MVP+RxJava+Retrofit的封装使用
由于Retrofit已经封装在DataManager中,在DataManager中有详细的介绍,这里只是提供一个例子让大家了解如何使用MVP+RxJava+Retrofit。
1.在使用Retrofit请求网络之前需要进行配置,在框架中提供了了Config配置类
框架中的Config总览如下:
public class Config {
/**必传参数**/
//是否为BuildConfig.DEBUG,日志输出需要
public static boolean DEBUG;
//设置Context
public static Context CONTEXT;
/**Retrofit**/
//网络请求的域名
public static String URL_DOMAIN;
//网络缓存地址
public static String URL_CACHE;
//设置OkHttp的缓存机制的最大缓存时间,默认为一天
public static long MAX_CACHE_SECONDS= 60 * 60 * 24;
//缓存最大的内存,默认为10M
public static long MAX_MEMORY_SIZE=10 * 1024 * 1024;
//设置网络请求json通用解析类
public static Class MClASS;
/**SharePreference**/
public static String USER_CONFIG;
/**Realm**/
public static RealmMigration realmMigration;
public static int realmVersion=0;
public static String realmName="myRealm.realm";
}
复制代码
在项目中需要根据项目需要进行配置,在Application中设置
private void config(){
Config.DEBUG= BuildConfig.DEBUG;//这个如果是测试时,日志输出,网络请求相关信息输出
Config.URL_CACHE=AppConfig.URL_CACHE;//OkHttp缓存地址
Config.CONTEXT=this;//这个是必传
Config.MClASS= Result.class;//如果项目的json数据格式统一可以设置一个统一的been类
Config.URL_DOMAIN="http://api.tianapi.com/";//网络请求域名
}
复制代码
根据项目需要定义一个通用的数据实体类,这是本例通用实体类,这个类需要设置到Applicatin中
public class Result implements Serializable {
private int code;
private String msg;
private T newslist;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getNewslist() {
return newslist;
}
public void setNewslist(T newslist) {
this.newslist = newslist;
}
}
复制代码
温馨提醒:由于每个项目返回来的json数据格式有所不同,如果Result中代表的字段例如newslist没有内容返回来的时候这个字段需要后台控制不返回,如果不做处理会报解析错误。
3.MVP+RxJava+Retrofit+OkHttp的缓存机制
上面的缓存配置完成之后通过以下代码即可:
public class WeChatWorldNewsPresenter extends WeChatWorldNewsContract.Presenter {
@Override
public void requestWorldNews(int page, int num) {
RequestBuilder>> resultRequestBuilder = new RequestBuilder<>(new RxObservableListener>>(mView) {
@Override
public void onNext(Result> result) {
mView.refreshUI(result.getNewslist());
}
});
resultRequestBuilder
.setUrl(ApiUrl.URL_WETCHAT_WORLD_NEWS)
.setTransformClass(WeChatNews.class)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setHttpTypeAndReqType(RequestBuilder.HttpType.DEFAULT_GET, RequestBuilder.ReqType.DEFAULT_CACHE_LIST)
.setParam("page",page)
.setParam("num",num);
rxManager.addObserver(DataManager.getInstance(DataManager.DataType.RETROFIT).httpRequest(resultRequestBuilder));
}
}
复制代码
4.MVP+RxJava+Retrofit+自定义磁盘缓存机制
public class WeChatChinaNewsDefinitionPresenter extends WeChatChinaNewsContract.Presenter {
@Override
public void requestChinaNews(int page, int num) {
String filePath = AppConfig.STORAGE_DIR + "wechat/china";
String fileName = "limttime.t";
RequestBuilder resultRequestBuilder = new RequestBuilder<>(new RxObservableListener>>(mView) {
@Override
public void onNext(Result> result) {
mView.refreshUI(result.getNewslist());
}
}).setFilePathAndFileName(filePath, fileName)
.setTransformClass(WeChatNews.class)
.setUrl(ApiUrl.URL_WETCHAT_CHINA_NEWS)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setHttpTypeAndReqType(RequestBuilder.HttpType.DEFAULT_GET,RequestBuilder.ReqType.DISK_CACHE_LIST_LIMIT_TIME)
.setParam("page", page)
.setParam("num", num);
rxManager.addObserver(DataManager.getInstance(DataManager.DataType.RETROFIT).httpRequest(resultRequestBuilder));
}
}
复制代码
注意:
①RxObservableListener有三个回调方法
void onNext(T result);
void onComplete();
void onError(NetWorkCodeException.ResponseThrowable e);
复制代码
只会重写onNext方法,其它两个方法可以自行选择重写。
②RxObservableListener提供两个构造函数
protected RxObservableListener(BaseView view){
this.mView = view;
}
protected RxObservableListener(BaseView view, String errorMsg){
this.mView = view;
this.mErrorMsg = errorMsg;
}
复制代码
这两个构造函数主要主要是为了统一处理onError的,如果要自定义错误提醒,则可以选择第二个构造函数。
③通过DataManager的网络请求方式会返回来一个DisposableObserver,需要把它通过rxManager.addObserver()添加进CompositeDisposable才能正常执行。
五、DataManager的使用(DataManager封装了三种数据请求方式,包括Retroift、SharePreference和Realm)
1.DataManager的了解
提供了三种方式
public enum DataType {
RETROFIT, REALM, SHAREPREFERENCE
}
复制代码
通过DataManager.getInstance(DataManager.DataType.XXX)可获得对应的请求方式。
1.DataManager的Retrofit请求
(1)配置
需要在项目的Application初始化Retrofit的一些参数
//基本配置
Config.DEBUG= BuildConfig.DEBUG;
Config.CONTEXT=this;
//Retrofit配置
Config.URL_CACHE=AppConfig.URL_CACHE;
Config.MClASS= Result.class;//如果项目的json数据格式统一可以设置一个统一的been类
Config.URL_DOMAIN="http://api.tianapi.com/";
复制代码
(2)使用统一解析类、不使用统一解析类、混合使用
① 如果项目如果项目的json数据格式统一可以设置一个统一的been类,例如上面的例子的Result类,同时要在Config类设置(下面例子都是有统一解析类):
RequestBuilder>> resultRequestBuilder = new RequestBuilder<>(new RxObservableListener>>(mView) {
@Override
public void onNext(Result> result) {
mView.refreshUI(result.getNewslist());
}
});
resultRequestBuilder
.setUrl(ApiUrl.URL_WETCHAT_FEATURED)
.setTransformClass(WeChatNews.class)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setParam("page",page)
.setParam("num",num);
rxManager.addObserver(DataManager.getInstance(DataManager.DataType.RETROFIT).httpRequest(resultRequestBuilder));
复制代码
②如果项目没有统一的解析been类,那么Config类就不用设置了,在Retrofit请求的时候直接指定一个解析类就可以了:
RequestBuilder resultRequestBuilder = new RequestBuilder<>(new RxObservableListener(mView) {
@Override
public void onNext(WeChatNewsResult result) {
mView.refreshUI(result.getNewslist());
}
});
resultRequestBuilder
.setUrl(ApiUrl.URL_WETCHAT_FEATURED)
.setTransformClass(WeChatNewsResult.class)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setParam("page",page)
.setParam("num",num);
rxManager.addObserver(DataManager.getInstance(DataManager.DataType.RETROFIT).httpRequest(resultRequestBuilder));
复制代码
③如果项目想两种方式共存,那么在请求的时候需要通过setUserCommonClass(false)设置才能不使用统一解析类进行解析:
RequestBuilder resultRequestBuilder = new RequestBuilder<>(new RxObservableListener(mView) {
@Override
public void onNext(WeChatNewsResult result) {
mView.refreshUI(result.getNewslist());
}
});
resultRequestBuilder
.setUrl(ApiUrl.URL_WETCHAT_FEATURED)
.setTransformClass(WeChatNewsResult.class)
.setUserCommonClass(false)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setParam("page",page)
.setParam("num",num);
rxManager.addObserver(DataManager.getInstance(DataManager.DataType.RETROFIT).httpRequest(resultRequestBuilder));
复制代码
注意:DISK_CACHE_LIST_LIMIT_TIME和DISK_CACHE_MODEL_LIMIT_TIME这两种限时使用缓存的请求方式不统一一种解析方式会出现页面没有数据显示,因为在限定的时间内如果突然转用另外一个解析实体类去解析会解析失败,只能等过限定时间或者清除本地缓存去解决这一问题。
(2)RequestBuilder的设置(网络请求的配置)
①数据处理的方式
public enum ReqType {
//没有缓存
NO_CACHE_MODEL,
No_CACHE_LIST,
//默认Retrofit缓存
DEFAULT_CACHE_MODEL,
DEFAULT_CACHE_LIST,
//自定义磁盘缓存,返回List
DISK_CACHE_LIST_LIMIT_TIME,
//自定义磁盘缓存,返回Model
DISK_CACHE_MODEL_LIMIT_TIME,
//自定义磁盘缓存,没有网络返回磁盘缓存,返回List
DISK_CACHE_NO_NETWORK_LIST,
//自定义磁盘缓存,没有网络返回磁盘缓存,返回Model
DISK_CACHE_NO_NETWORK_MODEL,
//保存网络数据到本地磁盘,可以设定网络请求是否返回数据
DISK_CACHE_NETWORK_SAVE_RETURN_MODEL,
DISK_CACHE_NETWORK_SAVE_RETURN_LIST,
}
复制代码
②网络请求方式
public enum HttpType {
//GET请求
DEFAULT_GET,
//POST请求
DEFAULT_POST,
//如果请求URL出现中文乱码,可选择这个
FIELDMAP_POST,
//上传一张图片
ONE_MULTIPART_POST
}
复制代码
③RequestBuilder的填充
RequestBuilder>> resultRequestBuilder = new RequestBuilder<>(new RxObservableListener>>(mView) {
@Override
public void onNext(Result> result) {
mView.refreshUI(result.getNewslist());
}
});
resultRequestBuilder
.setUrl(ApiUrl.URL_WETCHAT_WORLD_NEWS)
.setTransformClass(WeChatNews.class)
.setHttpTypeAndReqType(RequestBuilder.HttpType.DEFAULT_GET, RequestBuilder.ReqType.DEFAULT_CACHE_LIST)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setParam("page",page)
.setParam("num",num);
复制代码
④DataManager提供Retrofit请求的方法
DisposableObserver httpRequest(RequestBuilder requestBuilder);
复制代码
(3)Retrofit的扩展
如果存在DataManager提供的方法满足不了的请求可以通过RetrofitManager提供的getNoCacheApiService()和getApiService()获得不缓存和缓存的Retrofit,然后通过RxSubscriber进行回调。
Observable observable = RetrofitManager.getNoCacheApiService(ApiService.class)
.getWeChatStr(ApiUrl.URL_WECHAT_HOST + ApiUrl.ACCESS_TOKEN, reqParams);
DisposableObserver observer = observable
.compose(RxSchedulers.io_main())
.subscribeWith(new RxSubscriber() {
@Override
public void _onNext(WeChatAccessToken weChatAccessToken) {
getUserInfo(weChatAccessToken);
}
@Override
public void _onError(NetWorkCodeException.ResponseThrowable responseThrowable) {
showToast(R.string.wx_LoginResultEmpty);
hideLoadingDialog();
finish();
}
@Override
public void _onComplete() {
}
});
rxManager.addObserver(observer);
复制代码
定义一个ApiService类
public interface ApiService {
/**
* 微信精选
* @param url
* @param map
* @return
*/
@GET
Observable>> getWeChatFeaturedNews(@Url String url, @QueryMap Map map);
}
复制代码
(4)注意的问题
①请求的域名已经在Application设置好了,setUrl不需要填完整的url
②要区分清楚接口返回的数据时List还是Model,从而选择对应的ReqType
③setRequestParam可以设置参数集合,setParam可以单个设置
④使用DISK_CACHE_LIST_LIMIT_TIME/DISK_CACHE_MODEL_LIMIT_TIME这两个显示限时缓存时需要通过setFilePathAndFileName()设置保存路径setLimtHours()设置缓存时间(单位为:小时)
⑤如果要上传单张图片需要用到HttpType.ONE_MULTIPART_POST的请求方式,同时通过RequestBuilder设置MultipartBody.Part
2.DataManager的SharePreference的使用
(1)配置
需要在项目的Application初始化SharePreference的一些参数
//SharePreference配置
Config.USER_CONFIG="Collection_User";
复制代码
(2)使用方法
DataManager.getInstance(DataManager.DataType.SHAREPREFERENCE).saveByKeyWithSP("user","这
String user=DataManager.getInstance(DataManager.DataType.SHAREPREFERENCE).queryByKeyWithSP("user",String.class);是一条测试的内容");
复制代码
(3)DataManager提供SharePreference请求的方法
//自定义保存的配置文件名、key
void saveByNameAndKeyWithSP(String name, String key, Object object);
//使用在Application配置的保存文件名
void saveByKeyWithSP(String key,Object object);
//查询保存在自定义的配置文件的内容
T queryByNameAndKeyWithSP(String name, String key, Class clazz);
//查询保存在Application设置的文件的内容
T queryByKeyWithSP(String key, Class clazz);
复制代码
3.DataManager的Realm的使用
(1)配置
①需要在项目的Application初始化Realm的一些参数
//Realm的配置
Config.realmVersion=0;
Config.realmName="realm.realm";
Config.realmMigration=customMigration;//数据库数据迁移(been类字段增加移除)
复制代码
②在Project 的build.gradle中的dependencies加入
classpath "io.realm:realm-gradle-plugin:5.0.0"
复制代码
③在项目 的build.gradle中的顶部加入
apply plugin: 'realm-android'
复制代码
(2)使用方法
DataManager.getInstance(DataManager.DataType.REALM).saveOrUpdateWithPKByRealm(user);
user= (User) DataManager.getInstance(DataManager.DataType.REALM).queryFirstByRealm(User.class);
复制代码
(3)DataManager提供Realm请求的方法
/**
* 保存操作
*/
void saveOrUpdateWithPKByRealm(final RealmObject bean);
void saveOrUpdateWithPKByRealm(final List extends RealmObject> beans);
void saveWithoutPKByRealm(final RealmObject bean);
void saveWithoutPKByRealm(final List extends RealmObject> beans);
/**
* 查询操作
*/
RealmObject queryFirstByRealm(Class extends RealmObject> clazz);
RealmObject queryAllWithFieldByRealm(Class extends RealmObject> clazz, String fieldName, String value);
List extends RealmObject> queryAllByRealm(Class extends RealmObject> clazz);
List extends RealmObject> queryAllWithSortByRealm(Class extends RealmObject> clazz, String fieldName,Boolean isAscendOrDescend);
/**
* 修改操作
*/
void updateParamWithPKByRealm(Class extends RealmObject> clazz, String primaryKeyName, Object primaryKeyValue, String fieldName,Object newValue);
/**
* 删除操作
*/
void deleteFirstByRealm(Class extends RealmObject> clazz);
void deleteAllByRealm(Class extends RealmObject> clazz);
复制代码
(4)Realm数据迁移(been类字段增加移除)
随着app版本的迭代,数据库的字段可能会增加或者移除这时候就需要用到Realm提供的RealmMigration进行设置。
public class CustomMigration implements RealmMigration {
@Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
RealmSchema schema = realm.getSchema();
if (oldVersion == 0 && newVersion == 1) {
RealmObjectSchema personSchema = schema.get("User");
personSchema
.addField("age", int.class);
oldVersion++;
}else if(oldVersion == 1&&newVersion==2){
RealmObjectSchema personSchema = schema.get("User");
personSchema
.addField("address", String.class);
oldVersion++;
}
}
}
复制代码
步骤:
- 自定义RealmMigration,在migrate方法中进行字段的增加或者移除。
- 在Application中升Realm的版本号Config.realmVersion往上增加。
- 在Application设置RealmMigration,Config.realmMigration=customMigration。
(5)注意的问题
- 自定义Realm的保存文件文成的时候需要以.realm为后缀。
六、 Base的使用
1.Base封装了MVP和项目的基类
(1)MVP
- BaseModel
- BaseView
- BasePresenter
(2)在项目中的网络请求+MVP的完整实现
①定义一个contract类,内部分别继承上面的MVP base类,在这里定义操作。
public interface WeChatChinaNewsContract {
interface View extends BaseView {
void refreshUI(List weChatNews);
}
abstract class Presenter extends BasePresenter {
public abstract void requestChinaNews(int page,int num);
}
}
复制代码
②Presenter的具体执行类。
public class WeChatChinaNewsPresenter extends WeChatChinaNewsContract.Presenter {
@Override
public void requestChinaNews(int page, int num) {
RequestBuilder resultRequestBuilder = new RequestBuilder<>(new RxObservableListener>>(mView) {
@Override
public void onNext(Result> result) {
mView.refreshUI(result.getNewslist());
}
}).setUrl(ApiUrl.URL_WETCHAT_CHINA_NEWS)
.setTransformClass(WeChatNews.class)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setParam("page", page)
.setParam("num", num);
rxManager.addObserver(DataManager.getInstance(DataManager.DataType.RETROFIT).httpRequest(resultRequestBuilder));
}
}
复制代码
③UI
public class FragmentChinaNews extends BaseFragment implements WeChatChinaNewsContract.View{
@Override
public void init() {
}
@Override
public void requestData() {
((WeChatChinaNewsPresenter)mPresenter).requestChinaNews(pageSize,PAGE_SIZE);
}
@Override
public void refreshUI(List newsList) {
}
@Override
public void onError(String errorMsg) {
}
}
复制代码
2.UI Base
(1)IBaseActivity
-
IBaseActivity:主要提供了一个页面的基本方法、处理了MVP之间的关联、使用者可以直接继承该类使用、也可以继承该类实现扩展。
-
IBaseActivity
已经进行MVP之间的传递和关联。 -
处理好页面销毁之后Observables 和 Subscribers的解绑。
-
getLayoutId()设置布局、init()数据初始化、requestData()请求数据,执行顺序已经在IBaseActivity做好处理。
-
可以继承IBaseActivity进行扩展。
public abstract class BaseActivity
extends IBaseActivity{ private Unbinder unbinder; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); unbinder= ButterKnife.bind(this); } 复制代码
}
缺陷:如果对IBaseActivity进行扩展,在具体调用时需要类型才能调用相关方法。
@Override
public void requestData() {
((WeChatFeaturedPresenter) mPresenter).requestFeaturedNews(pageSize, PAGE_SIZE);
}
复制代码
(2)IBaseFragment
-
IBaseFragment:主要提供一个页面的基本方法,处理了MVP之间的关联,该类已经加入了懒人加载的控制方式、使用者可以直接继承该类使用、也可以继承该类实现扩展。
-
IBaseFragment
已经进行MVP之间的传递和关联。 -
处理好页面销毁之后Observables 和 Subscribers的解绑。
-
加入了懒人加载方式,只有页面显示才会调用requestData()请求数据,并只会调用一次。
-
getLayoutId()设置布局、init()数据初始化、requestData()请求数据,执行顺序已经在IBaseFragment做好处理。
-
可以继承IBaseFragment进行扩展。
public abstract class BaseFragment
extends IBaseFragment { private Unbinder unbinder; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); unbinder= ButterKnife.bind(this,mainView); return mainView; } } 复制代码
缺陷:如果对IBaseActivity进行扩展,在具体调用时需要类型才能调用相关方法。
@Override
public void requestData() {
((WeChatChinaNewsPresenter)mPresenter).requestChinaNews(pageSize,PAGE_SIZE);
}
复制代码
3.UI状态控制StateView的使用
(1)StateView的四种状态:
//不显示
public static final int STATE_NO_DATA = 0;
//正在加载
public static final int STATE_LOADING = 1;
//空数据
public static final int STATE_EMPTY = 2;
//没有网络
public static final int STATE_DISCONNECT=3;
复制代码
(2)StateView的使用:
①定义一个通用布局
复制代码
②添加到Ui页面的layout中
复制代码
注意:上面的语句添加的layout最外层最好是LinearLayout以及设置为android:orientation="vertical"
③通过以下语句进行状态切换
stateView.showViewByState(StateView.STATE_LOADING);
stateView.showViewByState(StateView.STATE_EMPTY);
stateView.showViewByState(StateView.STATE_NO_DATA);
stateView.showViewByState(StateView.STATE_DISCONNECT);
复制代码
④通过以下语句可以修改不同布局的内容以及样式
app:emptyText=""//设置空数据的文字提示
app:emptyImage=""//设置空数据显示的图片
app:disConnectImage=""//设置无网络的显示图片
app:disConnectText=""//设置无网络的文字提示
app:loadingText=""//设置loading的文字提示
app:loadingViewAnimation=""//设置loading的动画文件,只是ImageView
app:tipTextColor=""//设置文字显示颜色
app:tipTextSize=""//设置文字的大小
复制代码
3.三步实现Permission(权限)设置
(1)设置好要请求的权限
// 项目的必须权限,没有这些权限会影响项目的正常运行
private static final String[] PERMISSIONS = new String[]{
Manifest.permission.READ_SMS,
Manifest.permission.RECEIVE_WAP_PUSH,
};
复制代码
(2)权限通过PermissionManager管理
PermissionManager permissionManager=PermissionManager.with(this).
//必须权限
setNecessaryPermissions(PERMISSIONS);
//通过以下语句进行请求
permissionManager.requestPermissions();
复制代码
(3)页面重写onRequestPermissionsResult
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PermissionManager.PERMISSION_REQUEST_CODE) {//PERMISSION_REQUEST_CODE为请求权限的请求值
//有必须权限选择了禁止
if (permissionManager.getShouldShowRequestPermissionsCode() == PermissionManager.EXIST_NECESSARY_PERMISSIONS_PROHIBTED) {
showToast("可以在这里设置重新跳出权限请求提示框");
} //有必须权限选择了禁止不提醒
else if (permissionManager.getShouldShowRequestPermissionsCode() == PermissionManager.EXIST_NECESSARY_PERMISSIONS_PROHIBTED_NOT_REMIND) {
showToast("可以在这里弹出提示框提示去应用设置页开启权限");
permissionManager.startAppSettings();
}
}
}
复制代码
注意:如果有需求先判断是否所有权限都已经允许之后再进入主页面可以通过permissionManager.isLackPermission()进行判断,如果返回true则进行权限请求,如果返回false则进入主页面。
- 多个权限请求如果其中某一个被禁止提醒,会先把没有禁止提醒的权限处理完之后再进行处理。
- 如果是必要权限被禁止而没有选择禁止提醒退出之后下次会重新请求权限。
- 如果必要权限被禁止和选择了禁止提醒重新进入页面在onRequestPermissionsResult会重新回调方法。
- 使用者可以根据onRequestPermissionsResult()方法中返回来的标志PermissionManager.EXIST_NECESSARY_PERMISSIONS_PROHIBTED和PermissionManager.EXIST_NECESSARY_PERMISSIONS_PROHIBTED_NOT_REMIND做出对应的显示和操作(例如弹框提示跳转到设置页面或者toat提示)。
4.提供几种比较常用的Dialog弹框
①提供的Dialog
- DIALOG_TEXT_TWO_BUTTON_DEFAULT:默认弹窗样式。
- DIALOG_TEXT_TWO_BUTTON_CUSTOMIZE:自定义弹出按钮提示。
- DIALOG_LOADING_PROGRASSBAR:默认加载弹框。
- DIALOG_DISPLAY_ADVERTISING:显示广告图的弹框样式。
- DIALOG_CHOICE_ITEM:单项选择弹框样式。
②自定义Dialog样式
- 继承BaseDialog,通过setContentView(R.layout.dialog_list);设置弹窗布局。
- 在提供的initUI()方法中进行相应的逻辑设置。
③BaseDialog提供的方法
- setContentView():设置弹框布局样式。
- show():显示弹框。
- isShowing():判断弹框是否显示。
- dismiss():弹框销毁。
- setCancelable():点击返回键和外部不可取消。
- setDialogCancel():点击返回键可以取消。
5.提供几种比较常用的PopupWindow弹框
①BasePopupWindow提供的方法
- BasePopupWindow(Context context) :调用该构造函数默认弹出框铺满全屏。
- BasePopupWindow(Context context, int w, int h):调用该构造函数可指定弹出框大小。
- showPopup():在屏幕中央显示弹框。
- showPopupAsDropDown(View anchor):在指定控件底部显示弹框。
- setShowMaskView(boolean isShowMaskView):设置是否显示遮层。
- dismiss():销毁弹出框。
- getPopupLayoutRes():自定义弹出框的布局文件。
- getPopupAnimationStyleRes():自定义弹出框的动画文件。
②自定义PopupWindow
-
继承BasePopupWindow。
-
通过getPopupLayoutRes(R.layout.xxx)设置弹窗布局。
-
通过getPopupAnimationStyleRes(R.style.xxx)设置弹窗动画,不需要动画可以忽略不设置。
复制代码
-
如果需要显示遮层,在构造函数通过setShowMaskView(true)设置。
6.使用DisplayUtils修改状态栏
- setStatusBarFullTranslucentWithBlackFont(Activity act):状态栏透明黑字。
- setStatusBarBlackFontBgColor(Activity activity,int bgColor):修改状态栏颜色同时字体变为黑色。
- setStatusBarFullTranslucent(Activity act):状态栏透明。
- setStatusBarColor(Activity activity, int colorResId):改变状态栏颜色。
7.提供几种比较常用的Utils工具类
- DisplayUtils:px和dp的转换、获取屏幕高宽、状态栏白底黑字、设置状态栏颜色、设置状态栏全屏透明、获取状态栏的高度、获取ActionBar的高度。
- FileUtils:写文件、读取文本文件中的内容、判断缓存是否失效、检查文件是否存在、删除目录、检查是否安装SD卡、删除文件。
- GlideUtils:Glide显示网络图片、Glide实现高斯模糊。
- LogUtils:日志工具类。
- NetworkUtils:网络工具类。
- ToastUtils:Toast提示类。
七、 CustomView的使用
1.CommonTabLayout的使用
①属性:
- tab_tabIndicatorWidth:设置下滑线的长度。
- tab_tabIndicatorHeight:设置下滑线的高度。
- tab_tabIndicatorColor:下滑线颜色。
- tab_indicator_marginLeft/tab_indicator_marginRight/tab_indicator_marginTop/tab_indicator_marginBottom:设置下滑线外边距。
- tab_tabTextColor:没选中字体颜色。
- tab_tabTextSize:字体大小。
- tab_tabSelectedTextColor:选中字体颜色。
- tab_padding:下滑线内边距,block样式时可以通过该属性设置距离。
- tab_tabBackground:Tab的背景颜色。
- tab_indicator_corner:下滑线的圆角大小。
- tab_indicator_gravity(bottom、top):设置下滑线显示的位置,只针对line和triangle。
- tab_tabMode(scrollable、fixed):Tab的显示模式。
- tab_indicator_style(line、triangle、block):下滑线的样式。
②具体用户可参照例子使用。
2.OutSideFrameTabLayout的使用
①属性:
- tab_tabIndicatorColor:设置Tab颜色。
- tab_indicator_corner:圆角大小
- tab_indicator_marginLeft/tab_indicator_marginRight/tab_indicator_marginTop/tab_indicator_marginBottom:设置下滑线外边距。
- tab_tabTextColor:没选中字体颜色。
- tab_tabTextSize:字体大小。
- tab_tabSelectedTextColor:选中字体颜色。
- tab_padding:内边距。
- tab_bar_color:bar的背景颜色。
- tab_bar_stroke_color:外框的颜色。
- tab_bar_stroke_width:外框的大小。
- tab_width:bar的长度。