主页面上拉刷新实现
by seaicelin
转载请注明出处:http://blog.youkuaiyun.com/amd123456789/article/details/52331481
接着上一篇的问题:
- RecylerView 如何上拉加载更多条目?
我们来理一下这个思路吧,看看需要我们来实现什么?
- UI上需要在RecylerView列表上添加ScrollListener,增加上拉的逻辑判断,并添加实现?
- RecylerView滑动的过程中,ToolBar的Title文本需要更新为当天日报条目对应的日期?
- RecylerView需要对两种条目进行处理,显示日期和当前日期日报条目这两种Item?
- 下拉过程中,对前一天条目Json数据的获取和保存怎么实现?
以上就是我遇到的问题,一个一个解决吧!
1. 上拉逻辑实现
首先,上拉的过程中,需要到最后一个条目才会去加载。因此我们需要判断此时RecylerView可见的最后一个条目位置,是不是刚好等于总的条目数。
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
//super.onScrollStateChanged(recyclerView, newState);
LogUtils.e("onScrollStateChanged");
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
int visibleCount = layoutManager.getChildCount();
int totalCount = layoutManager.getItemCount();
// 四个条件,分别是
// 1. 是否有数据,
// 2. 状态是否是滑动停止状态,
// 3. 显示的最大条目是否大于整个数据(注意偏移量),
// 4. 是否正在加载数据
if (visibleCount > 0 && newState == RecyclerView.SCROLL_STATE_IDLE
&& lastVisiableItemPos >= totalCount - 1 && !isLoadingData) {
if (listener != null) {
isLoadingData = true;
listener.loadMore();
}
}
}
2. ToolBar Title的实时更新
针对这个问题,无非就是在Recylerview滑动的过程中,实时更新它。问题的关键其实是怎么获取到这个Item所对应的日期。很简单,我们在HomeStoriesInfo这个类中,添加两个成员,
1. 一个是存储当天条目的所对应的日期
2. 一个是当前条目是否是显示日期标志位。
这样一来,增加这两个成员变量就同时解决了第三个问题:
- RecylerView需要对两种条目进行处理,显示日期和当前日期日报条目这两种Item?
通过对成员变量的判断进行区分,同时HomeStoriesInfoList可以存储这个日期,就不用在另外做区分对待。
public class HomeStoriesInfo {
...
private boolean isDateTime;//是否是日期条目,是就用来对应DateHolder
private String date;//存储当前条目是那个日期,Toolbar更新Title就是用这个
...
}
好了,知道怎么解决这几个问题,咱就先来把这三个UI的问题给解决了。实际上就是对RecylerView的OnScrollListener进行修改,把这个类重新来实现:
package com.example.seaice.zhihuribao.view;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import com.example.seaice.zhihuribao.Utils.LogUtils;
/**
* Created by seaicelin on 2016/8/25.
*/
public class LoadDataScrollController extends RecyclerView.OnScrollListener implements SwipeRefreshLayout.OnRefreshListener {
public static final int LINEAR_LAYOUT = 0;
public static final int GRID_LAYOUT = 1;
public static final int STRAGGERED_GRID_LAYOUT = 2;
private int layoutType = 0;
private int lastVisiableItemPos;//最后可见条目位置
private int firstVisibleItemPos;//第一条可见条目位置
private int[] lastPos;
private boolean isLoadingData = false;//是否正在加载数据
private OnRecycleRefreshListener listener;//外部需要实现的接口
public LoadDataScrollController(OnRecycleRefreshListener listener) {
this.listener = listener;
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
LogUtils.e("onScrollStateChanged");
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
int visibleCount = layoutManager.getChildCount();
int totalCount = layoutManager.getItemCount();
LogUtils.e("visibleCount = " + visibleCount);
LogUtils.e("totalCount = " + totalCount);
LogUtils.e("lastVisiableItemPos = " + lastVisiableItemPos);
LogUtils.e("newState = " + newState);
LogUtils.e("isLoadingData = " + isLoadingData);
// 四个条件,分别是
// 1. 是否有数据,
// 2. 状态是否是滑动停止状态,
// 3. 显示的最大条目是否大于整个数据(注意偏移量),
// 4. 是否正在加载数据
if (visibleCount > 0 && newState == RecyclerView.SCROLL_STATE_IDLE
&& lastVisiableItemPos >= totalCount - 1 && !isLoadingData) {
if (listener != null) {
isLoadingData = true;
listener.loadMore();
}
}
}
public void setLoadingDataStatus(boolean isLoadData) {
this.isLoadingData = isLoadData;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
//super.onScrolled(recyclerView, dx, dy);
LogUtils.e("onScrolled");
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
layoutType = LINEAR_LAYOUT;
} else if (layoutManager instanceof GridLayoutManager) {
layoutType = GRID_LAYOUT;
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
layoutType = STRAGGERED_GRID_LAYOUT;
}
switch (layoutType) {
case LINEAR_LAYOUT:
lastVisiableItemPos = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
firstVisibleItemPos = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
break;
case GRID_LAYOUT:
lastVisiableItemPos = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
firstVisibleItemPos = ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition();
break;
case STRAGGERED_GRID_LAYOUT:
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
if (lastPos == null) {
lastPos = new int[staggeredGridLayoutManager.getSpanCount()];
}
staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(lastPos);
lastVisiableItemPos = findMax(lastPos);
firstVisibleItemPos = 1;
break;
}
//更新toolbar title
if (listener != null) {
listener.updateTitle(firstVisibleItemPos);
}
}
private int findMax(int[] lastPos) {
int max = lastPos[0];
for (int value : lastPos) {
if (value > max) {
max = value;
}
}
return max;
}
//接口
public interface OnRecycleRefreshListener {
void refresh();
void loadMore();
void updateTitle(int pos);
}
@Override
public void onRefresh() {
if (listener != null) {
isLoadingData = true;
listener.refresh();
}
}
}
可以看到,这里我们把OnRecyclerRefreshListener都交给外部实现,这里只是暴露几个方法。ContentActivity来实现这个接口,具体UI都是在这里实现:
//下拉刷新
@Override
public void refresh() {
BaseApplication.getHandler().postDelayed(new Runnable() {
@Override
public void run() {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(BaseApplication.getApplication(), "刷新成功", Toast.LENGTH_SHORT).show();
}
}, 3000);
}
//加载更多
@Override
public void loadMore() {
Runnable runnable = new Runnable() {
@Override
public void run() {
OldNewsProtocol protocol = new OldNewsProtocol();
final List<HomeStoriesInfo> homeInfo = protocol.loadData();
if (homeInfo != null) {
UiUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
loadDataCtl.setLoadingDataStatus(false);
recylerViewAdapter.add(homeInfo);
}
});
}
}
};
ThreadMgr.getThreadPool().execute(runnable);
}
@Override
public void updateTitle(int pos) {
if (pos == 0) {
toolbar.setTitle(UiUtils.getString(R.string.home_page));
} else {
toolbar.setTitle(recylerViewAdapter.getTitle(pos-1));
}
}
上拉刷新数据的获取和保存
之前主页面和引导页面分别用HomeProtocol和GuideProtocol来加载,这次我们同样生成一个OldNewsProtocol用来加载以前的数据。并且这次数据并不包含HomeTopInfo相关,只有HomeStoriesInfo对应的json数据。
package com.example.seaice.zhihuribao.protocol;
import android.text.format.DateFormat;
import android.text.format.Time;
import com.example.seaice.zhihuribao.Utils.LogUtils;
import com.example.seaice.zhihuribao.Utils.UiUtils;
import com.example.seaice.zhihuribao.bean.HomeStoriesInfo;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
/**
* Created by seaicelin on 2016/8/26.
*/
public class OldNewsProtocol extends BaseProtocol<List<HomeStoriesInfo>> {
private String HOMEURL = "http://news-at.zhihu.com/api/4/news/before/";
private String HOMECACHE;
private static int oldNewsDateCount = 0;//没加载一次增加,表示加载了之前的多少天了
public OldNewsProtocol() {
//1. 初始化加载的是哪一天
//2. 初始化HOMECACHE,即保存json的本地文件路径
initDateToGetNews();
}
@Override
protected List<HomeStoriesInfo> paserJsonData(String json) {
LogUtils.e("HomeProtocol paserJsonData");
try {
JSONObject jsonObject = new JSONObject(json);
String date = jsonObject.getString("date");
JSONArray jsonArrayStories = jsonObject.getJSONArray("stories");
List<HomeStoriesInfo> homeStoriesInfos = new ArrayList<>();
HomeStoriesInfo dateInfo = new HomeStoriesInfo();
dateInfo.setDate(formatJsonDate(date));//格式化日期
dateInfo.setIsDateTime(true);
homeStoriesInfos.add(dateInfo);
for (int i = 0; i < jsonArrayStories.length(); i++) {
JSONObject object = jsonArrayStories.getJSONObject(i);
int type = object.getInt("type");
String id = object.getString("id");
String ga_prefix = object.getString("ga_prefix");
String title = object.getString("title");
String images = object.getString("images");
String image = images.substring(2, images.length() - 2);
image = image.replace("\\", "");//去掉这个斜杠才能得到数据,take me a lot of time
HomeStoriesInfo homeStoriesInfo = new HomeStoriesInfo(image, type, id, ga_prefix, title);
homeStoriesInfo.setDate(formatJsonDate(date));
homeStoriesInfo.setIsDateTime(false);
LogUtils.e(homeStoriesInfo.toString());
homeStoriesInfos.add(homeStoriesInfo);
}
return homeStoriesInfos;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
@Override
protected String getCacheDir() {
return HOMECACHE;
}
@Override
protected String getUrl() {
return HOMEURL;
}
//下拉加载日报的日期
private void initDateToGetNews() {
Calendar calendar = Calendar.getInstance();
Time time = new Time();
time.setToNow();
calendar.setTimeInMillis(time.toMillis(false) - 24 * 60 * 60 * 1000 * oldNewsDateCount);
DateFormat dateFormat = new DateFormat();
oldNewsDateCount++;
String date = dateFormat.format("yyyyMMdd", calendar.getTimeInMillis()).toString();
HOMEURL += date;//对应加载哪天的数据
HOMECACHE += date;//保存以日期作为文件路径
}
//格式化日期,保存为xx年xx月xx日 星期几
private String formatJsonDate(String date) {
String sb = "";
int year = Integer.valueOf(date.substring(0, 4));
int month = Integer.valueOf(date.substring(4, 6));
int day = Integer.valueOf(date.substring(6));
sb += (year + "年" + month + "月" + day + "日");
Calendar calendar = Calendar.getInstance();
calendar.set(year, month - 1, day);
String dayOfWeek = UiUtils.getDayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));
sb += (" " + "星期" + dayOfWeek);
return sb.toString();
}
}
恩,以上基本就完成了加载数据的功能了,再来贴一下RecylerView Adapter的代码,基本上就明白了,这几个问题就这么解决了:
package com.example.seaice.zhihuribao.adapter;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.example.seaice.zhihuribao.R;
import com.example.seaice.zhihuribao.Utils.BaseApplication;
import com.example.seaice.zhihuribao.Utils.LogUtils;
import com.example.seaice.zhihuribao.bean.HomeInfo;
import com.example.seaice.zhihuribao.bean.HomeStoriesInfo;
import com.example.seaice.zhihuribao.bean.HomeTopInfo;
import com.example.seaice.zhihuribao.view.CircleIndicator;
import com.squareup.picasso.Picasso;
import java.util.List;
import butterknife.ButterKnife;
/**
* Created by seaice on 2016/8/22.
*/
public class HomeRecylerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private static final int TYPE_DATE = 2;
private LayoutInflater mInflate;
private Context mContext;
private List<HomeStoriesInfo> homeStoriesInfos;
private List<HomeTopInfo> homeTopInfos;
private List<HomeInfo> homeInfoList;
private boolean isTouchViewPager = false;
private boolean isViewPagerLoop = false;
public HomeRecylerViewAdapter(List<HomeInfo> homeInfo) {
this.homeInfoList = homeInfo;
initData();
}
private void initData() {
HomeInfo homeInfo = homeInfoList.get(0);
homeStoriesInfos = homeInfo.getHomeStoriesInfos();
homeTopInfos = homeInfo.getHomeTopInfoList();
mContext = BaseApplication.getApplication();
mInflate = LayoutInflater.from(mContext);
LogUtils.e(homeStoriesInfos.toString());
}
//绑定不同类型Holder
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {//view pager
View view = mInflate.inflate(R.layout.header_recyler_view, parent, false);
return new HeaderHolder(view);
} else if (viewType == TYPE_DATE) {//显示日期
View view = mInflate.inflate(R.layout.date_recyler_view, parent, false);
return new DateHolder(view);
} else if (viewType == TYPE_ITEM) {//显示具体条目
View view = mInflate.inflate(R.layout.item_recyler_view, parent, false);
return new MyViewHolder(view);
}
throw new RuntimeException("there is no type that match the type " + viewType);
}
//设置holder的view元素
@Override
public void onBindViewHolder(RecyclerView.ViewHolder vh, int position) {
if (vh instanceof HeaderHolder) {
if (isViewPagerLoop == false) {
isViewPagerLoop = true;
setViewPager((HeaderHolder) vh);
}
} else if (vh instanceof DateHolder) {
HomeStoriesInfo homeStoriesInfo = getHomeStoriesListItem(position);
DateHolder holder = (DateHolder) vh;
if (position != 1) {
holder.tv_date.setText(homeStoriesInfo.getDate());
} else {
holder.tv_date.setText("今日热闻");
}
} else if (vh instanceof MyViewHolder) {
HomeStoriesInfo info = getHomeStoriesListItem(position);
MyViewHolder holder = (MyViewHolder) vh;
holder.tv_title.setText(info.getTitle());
Picasso.with(BaseApplication.getApplication()).load(info.getImages()).into(holder.iv_title);
} else {
throw new RuntimeException("there is no holer that match the " + vh);
}
}
@Override
public int getItemCount() {
int itemCount = homeStoriesInfos.size() + 1;
LogUtils.e("getItemCount = " + itemCount);
return itemCount;
}
@Override
public int getItemViewType(int position) {
if (isPositionHeader(position)) {
return TYPE_HEADER;
} else if (isPositionDate(position)) {
return TYPE_DATE;
} else {
return TYPE_ITEM;
}
}
//判断是否是日期条目位置
private boolean isPositionDate(int position) {
HomeStoriesInfo homeStoriesInfo = getHomeStoriesListItem(position);
if (homeStoriesInfo.isDateTime()) {
return true;
} else {
return false;
}
}
private boolean isPositionHeader(int position) {
return position == 0;
}
//设置viewPager
private void setViewPager(final HeaderHolder holder) {
holder.viewPager.setAdapter(new HomeViewPagerAdapter(homeTopInfos));
holder.indicator.setCircleNumber(homeTopInfos.size());
holder.viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
holder.indicator.setSelectdItem(position);
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_DRAGGING) {
isTouchViewPager = true;
} else {
isTouchViewPager = false;
}
}
});
loopViewPager(holder);
}
//获取HomeStoriesList列表的条目,需要减去HeaderView
private HomeStoriesInfo getHomeStoriesListItem(int pos) {
if (pos > 0) {
return homeStoriesInfos.get(pos - 1);
} else {
throw new RuntimeException("the pos < 0");
}
}
//让viewpager循环播放
private void loopViewPager(final HeaderHolder holder) {
BaseApplication.getHandler().postDelayed(new Runnable() {
@Override
public void run() {
if (isTouchViewPager == false) {
int item = (holder.viewPager.getCurrentItem()) + 1;
holder.viewPager.setCurrentItem(item % (holder.viewPager.getAdapter().getCount()));
holder.indicator.setSelectdItem(item);
}
loopViewPager(holder);
}
}, 5000);
}
//显示日报条目的Holder
public class MyViewHolder extends RecyclerView.ViewHolder {
public ImageView iv_title;
public TextView tv_title;
public MyViewHolder(View view) {
super(view);
iv_title = ButterKnife.findById(view, R.id.iv_title);
tv_title = ButterKnife.findById(view, R.id.tv_title);
}
}
//显示viewpager的Holder
public class HeaderHolder extends RecyclerView.ViewHolder {
public ViewPager viewPager;
public CircleIndicator indicator;
public HeaderHolder(View view) {
super(view);
viewPager = ButterKnife.findById(view, R.id.viewPager);
indicator = ButterKnife.findById(view, R.id.indicator);
}
}
//显示日期的Holder
public class DateHolder extends RecyclerView.ViewHolder {
public TextView tv_date;
public DateHolder(View view) {
super(view);
tv_date = ButterKnife.findById(view, R.id.tv_date);
}
}
//加载更多数据
public void add(List<HomeStoriesInfo> homeInfo) {
LogUtils.e("add!!");
if (homeInfo == null) {
return;
}
homeStoriesInfos.addAll(homeInfo);
this.notifyDataSetChanged();
}
public String getTitle(int pos) {
if (pos >= 0 && pos < homeStoriesInfos.size()) {
return homeStoriesInfos.get(pos).getDate();
}
return "";
}
}
下一篇要搞的问题是:
- 点击条目显示具体的知乎日报信息怎么实现?
上拉刷新参考: