本篇笔记是对上一篇Android入门学习——Retrofit+MVP模式学习的补充。这次加上了RxJava的简单使用,并在上一篇中特别简单的Demo的基础上加上了Swiperefreshlayout+RecyclerView的配合使用。加上了下拉刷新以及上拉加载更多。但上拉加载更多也只是个简单的思路,实现的并不好,需要以后再进行优化封装。本人菜鸟,讲解不了啥知识点,只是为了记录学习的过程。想学RxJava可以多看看扔物线大神的给Android开发者的RxJava详解
项目添加的依赖库:
java
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.android.support:recyclerview-v7:23.3.0'
compile 'com.google.code.gson:gson:2.2.4'
compile 'com.android.support:cardview-v7:23.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
**compile 'io.reactivex:rxjava:1.1.5'**
**compile 'io.reactivex:rxandroid:1.2.0'**
**compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'**
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.android.support:design:23.3.0'
比上篇多了io.reactivex:rxjava:1.1.5、io.reactivex:rxandroid:1.2.0、com.squareup.retrofit2:adapter-rxjava:2.0.2。使用RxJava这三个都需要添加,并且retrofit2、rxjava、rxandroid版本号要一致,我这里用的都是2.0.2。RxAndroid就是RxJava针对Android推出的。
与上一篇相比,工程的目录结构基本没大变化。还是继续尝试学习MVP模式下的目录分层。View和Presenter层基本没啥变化,请求数据的接口依然是上一篇中使用的接口。变化大的是使用Retrofit所创建的接口RetrofitService。
下面贴出RetrofitService的代码:
public interface RetrofitService {
@FormUrlEncoded
@POST(AppConfigs.URL_DATA)
Observable getDataBean(@FieldMap Map map);
}
代码很简单,就3行。和不用RxJava的区别就是接口中方法的返回值,不再是Retrofit中的Call而是换成了RxJava的Ovservable(被观察者)。
理所当然,Model层中的Retrofit网路请求就发生了重大改变。
public class ShowContentModel implements ShowContentBiz {
private Subscription sub;
private boolean state = false;
public boolean isState() {
return state;
}
@Override
public void onResult(final onShowContentListener onShowContentListener ,int p) {
Retrofit retrofit = new Retrofit.Builder().baseUrl(AppConfigs.URL_DATA)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
RetrofitService retrofitService = retrofit.create(RetrofitService.class);
Map map = new HashMap<>();
map.put(AppConfigs.APPID_NAME,AppConfigs.APPID);
map.put(AppConfigs.SECRECT_NAME,AppConfigs.SECRECT);
map.put(AppConfigs.PAGE_NAME,""+p);
map.put(AppConfigs.NUM_NAME,AppConfigs.NUM);
//Log.e("page","--"+p);
state = true;//设置为正在请求数据
sub = retrofitService.getDataBean(map)
.subscribeOn(Schedulers.io())//网络请求在io线程
.observeOn(AndroidSchedulers.mainThread())//subscribe()在UI线程
.subscribe(new Subscriber() {
@Override
public void onCompleted() {
state = false;
}
@Override
public void onError(Throwable e) {
state = false;
onShowContentListener.onFailed(e.getMessage());
}
@Override
public void onNext(DataBean dataBean) {
if (dataBean != null)
onShowContentListener.onSuccess(dataBean);
}
});
}
public void cancelRequest(){
if ( sub != null && !sub.isUnsubscribed()){
sub.unsubscribe();
}
}
}
Retrofit对象的创建加入了.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
这行代码。这行代码必须加,否则会报错。
retrofitService.getDataBean()这个方法的返回对象其实是Observable。只是方法链最后调用了subscribe()
这个方法,返回对象也就变就变成了Subscription。这里返回Subsciption这个对象的目的是为了使用isUnsubscribed()
以及unsubscribe()
这两个方法。
isUnsubscribed()
这个方法的返回值为Boolean类型,这个方法是用来判断被观察者是否已经取消了订阅。
unsubscribe()
这个方法则是取消订阅,也就是取消了网络请求。
在subscribe()
这个方法会先执行onNext()
这个方法。onCompleted()
和onError
这两个方法则是势不两立的,一个执行另外一个就不再执行。
RxJava和Retrofit简单配合使用就完成了。但RxJava最令人拍手叫好的flatMap,这里并没有用到。以后深入的学习后,再记录补充。
Swiperefreshlayout的使用相对比较方便,几行的代码量就可以实现下拉刷新。
在xml文件中:
"@+id/srfl_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
"@+id/rv_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
界面布局很简单就是一个SwipeRefreshLayout包裹一个RecyclerView。
在MainActvity中:
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.srfl_main);
swipeRefreshLayout.setColorSchemeColors(R.color.colorAccent);
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
swipeRefreshLayout.setRefreshing(true);
}
});
.setColorSchemeColors()
设置下拉刷新圆圈的颜色。
swipeRefreshLayout.setRefreshing(true)
设置下拉刷新状态,出现小圆圈在界面转。在数据请求完成的时候设置为false,小圆圈就会消失。
swipeRefreshLayout.post()
这个方法是为了实现有第一次进入界面的时候,有一种自动刷新的效果。
我尝试的是通过RecyclerView的Adapter来添加FooterView,也有通过自定义RecyclerView来添加的思路。
下面是完整的BaseRecyclerAdapter代码:
public abstract class BaseRecyclerAdapter <T> extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
protected List datas = new ArrayList<>();
protected final int mLayoutItemId;
protected boolean isScrolling;
protected Context mContext;
private OnRecyclerItemClickListener mListener;
//填充布局类型
private final int TYPE_NORMAL = 0 ;
private final int TYPE_HEADER = 1;
private final int TYPE_FOOTER = 2 ;
//FooterView 和 HeaderView
private View footerView;
private View headerView;
public BaseRecyclerAdapter(RecyclerView rv,int mLayoutItemId) {
this. mContext =rv.getContext();
this.mLayoutItemId = mLayoutItemId;
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
isScrolling = !(newState == RecyclerView.SCROLL_STATE_IDLE);
if (!isScrolling) {
notifyDataSetChanged();
}
}
});
}
public void setHeaderView(View headerView) {
this.headerView = headerView;
notifyItemInserted(0);
}
public void setFooterView(View footerView) {
this.footerView = footerView;
notifyItemInserted(getItemCount() - 1);
}
public void removedFooterView(){
if (footerView != null){
notifyItemRemoved(getItemCount()-1);
}
}
@Override
public int getItemViewType(int position) {
if (headerView !=null && position ==0){
return TYPE_HEADER;
}
if (footerView != null && position + 1 == getItemCount()) {
return TYPE_FOOTER ;
}
return TYPE_NORMAL ;
}
@Override
public int getItemCount() {
return datas.size() == 0? 0 :( footerView ==null ? 0:datas.size() + 1 );
}
public void setDataList(List datas){
if (datas != null){
this.datas.clear();
this.datas.addAll(datas);
notifyDataSetChanged();
}
}
public void setItemListener(OnRecyclerItemClickListener mListener){
this.mListener = mListener;
}
private View.OnClickListener getOnClickListener(final int position){
return new View.OnClickListener(){
@Override
public void onClick(View v) {
if (mListener != null & v != null){
mListener.onRecyclerItemClick(v,datas.get(position),position);
}
}
};
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(mContext);
RecyclerView.ViewHolder holder = null;
if (viewType == TYPE_FOOTER && footerView != null){
holder = new FooterOrHeaderViewHolder(footerView);
}else if(viewType == TYPE_HEADER && headerView != null){
holder = new FooterOrHeaderViewHolder(headerView);
}else{
View view = inflater.inflate(mLayoutItemId,parent,false);
holder =new RecyclerHolder(view) ;
}
holder.setIsRecyclable(true);
return holder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (getItemViewType(position) == TYPE_FOOTER || getItemViewType(position) == TYPE_HEADER){
return;
}
position = getRealPosition(holder);
bindViewData((RecyclerHolder) holder,datas.get(position),position,isScrolling);
holder.itemView.setOnClickListener(getOnClickListener(position));
}
private int getRealPosition(RecyclerView.ViewHolder viewHolder){
int position = viewHolder.getLayoutPosition();
return headerView == null ? position : (position - 1);
}
public abstract void bindViewData(RecyclerHolder holder, T item, int position, boolean isScrolling);
public interface OnRecyclerItemClickListener {
void onRecyclerItemClick(View view, Object data, int position);
}
private class FooterOrHeaderViewHolder extends RecyclerView.ViewHolder {
public FooterOrHeaderViewHolder(View view) {
super(view);
}
}
/**
* 当使用GridLayoutManager时添加HeaderView和FooterView会调用这个方法
*/
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if(manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (getItemViewType(position) == TYPE_FOOTER || getItemViewType(position) == TYPE_HEADER){
return gridManager.getSpanCount();
}else{
return 1;
}
}
});
}
}
/**
* 当使用瀑布流添加HeaderView的时候会调用这个方法
*/
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
super.onViewAttachedToWindow(holder);
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if(lp != null&& lp instanceof StaggeredGridLayoutManager.LayoutParams
&& (holder.getLayoutPosition() == 0||holder.getLayoutPosition() == (getItemCount()-1))) {
StaggeredGridLayoutManager.LayoutParams slp = (StaggeredGridLayoutManager.LayoutParams) lp;
slp.setFullSpan(true);
}
}
}
onAttachedToRecyclerView()
和onViewAttachedToWindow
这两个方法中的代码是固定的,是为了让添加的HeaderView或者FooterView能够单独占据一行。可以尝试在使用GridLayoutManager和瀑布流的时候不添加这两个方法,看看会出现啥情况。一下子就会搞明白这两个方法干嘛用的了。
在MainActivity中:
rv.setOnScrollChangeListener(new RecyclerView.OnScrollChangeListener(){
@Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
RecyclerView rView = (RecyclerView) v;
int lastVisible = ((GridLayoutManager)rView.getLayoutManager()).findLastVisibleItemPosition();
if (!presenter.getRefreshState() && rView.getAdapter().getItemCount() - 1 == lastVisible && lastVisible != mLastVisibleItem){
mLastVisibleItem = lastVisible;
presenter.getListData(++ page);
}
}
});
就是给RecyclerView设置滑动监听,判断条件是,是否正在进行数据的请求,最后一个item是否出现,最后一个itme是否为第一次出现。
这篇博客只是记载我的学习以及尝试。代码中的不足以后会修补,下面是源码。
又修改了一部分代码,加入了HeaderView
代码