首先看下原版知乎日报首页效果
我们需实现的效果有:
- 1.下拉刷新数据
- 2.滑动到底部加载下一页数据
- 3.随着列表页的滑动,头部的标题也发生变化.
好,有了需求,我们一步一步实现.重点是第三个效果.
1.首先,确定页面的布局
从上往下:toolBar
,下拉刷新控件swipeRefresh
,还是数据填充的列表页recycleView
.
笔者因为个人项目规划的原因,把toolbar放在了Mainactivity中.
所以布局的代码如下:
activity_main
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:id="@+id/drawerLayout"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
app:navigationIcon="@mipmap/toolbar_icon"
android:background="@color/appColor"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</android.support.v7.widget.Toolbar>
<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.ashokvarma.bottomnavigation.BottomNavigationBar
android:id="@+id/bottom_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<android.support.design.widget.NavigationView
android:layout_gravity="left"
android:id="@+id/navigation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:headerLayout="@layout/layout_navigation_view">
</android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>
fragment_zhihu_home
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/appbg"
android:focusable="true"
android:focusableInTouchMode="true">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toBottomOf="@+id/viewpager">
</android.support.v7.widget.RecyclerView>
</android.support.v4.widget.SwipeRefreshLayout>
2.实现下拉刷新效果
这个最简单,布局分析的时候就已经说到了采用swipeRefresh
下拉加载控件.swipeRefresh
包裹recycleView
,实现OnRefreshListener()
方法:
swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
presenter.getData(true);
}
});
3.实现滑动到底部加载下一页
其实也就是需要判断recycleview什么时候滑动到了底部.
在recycleview的滑动监听中去监听是否滑动到底部
recyclerview.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (isSlideToBottom(recyclerView)) {
presenter.loadNext();
}
}
});
判断是否滑动到底部
public static boolean isSlideToBottom(RecyclerView recyclerView) {
if (recyclerView == null) return false;
if (recyclerView.computeVerticalScrollExtent() + recyclerView.computeVerticalScrollOffset()
>= recyclerView.computeVerticalScrollRange())
return true;
return false;
}
computeVerticalScrollExtent()
表示显示区域的高度
computeVerticalScrollOffset()
表示已经向下滚动的距离,为0时表示已处于顶部。
computeVerticalScrollRange()
表示整体的高度,包括未显示的区域
判断recycleview是否滑动到底部的其他方法
4.随着列表页的滑动,头部的标题也发生变化.
本文的重点
说到这,可以分析下知乎日报的数据接口
这里用到了两个api:
1.最新消息api:
- 响应实例:
date: "20140523",
stories: [
{
title: "中国古代家具发展到今天有两个高峰,一个两宋一个明末(多图)",
ga_prefix: "052321",
images: [
"http://p1.zhimg.com/45/b9/45b9f057fc1957ed2c946814342c0f02.jpg"
],
type: 0,
id: 3930445
},
...
],
top_stories: [
{
title: "商场和很多人家里,竹制家具越来越多(多图)",
image: "http://p2.zhimg.com/9a/15/9a1570bb9e5fa53ae9fb9269a56ee019.jpg",
ga_prefix: "052315",
type: 0,
id: 3930883
},
...
]
}
- 分析:
date : 日期
stories : 当日新闻
title : 新闻标题
images : 图像地址(官方 API 使用数组形式。目前暂未有使用多张图片的情形出现,曾见无 images 属性的情况,请在使用中注意 )
ga_prefix : 供 Google Analytics 使用
type : 作用未知
id : url 与 share_url 中最后的数字(应为内容的 id)
multipic : 消息是否包含多张图片(仅出现在包含多图的新闻中)
top_stories : 界面顶部 ViewPager 滚动显示的显示内容(子项格式同上)(请注意区分此处的 image 属性与 stories 中的 images 属性)
2.过往消息
若果需要查询 11 月 18 日的消息,before 后的数字应为 20131119
知乎日报的生日为 2013 年 5 月 19 日,若 before 后数字小于 20130520 ,只会接收到空消息
输入的今日之后的日期仍然获得今日内容,但是格式不同于最新消息的 JSON 格式
- 响应实例:
{
date: "20131118",
stories: [
{
title: "深夜食堂 · 我的张曼妮",
ga_prefix: "111822",
images: [
"http://p4.zhimg.com/7b/c8/7bc8ef5947b069513c51e4b9521b5c82.jpg"
],
type: 0,
id: 1747159
},
...
]
}
- 格式与最新消息的相同
recycleview的多布局显示:
我们在列表页需要显示的内容
1.top_stories[]:头部的banner
2.date: "20140523":每一页数据的头部日期显示
3.stories[]:新闻列表
public class ZhihuHomeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static final int HEAD = 1;
private List<ZhihuBanberBean> zhihuBanberBeans;
private List<Object> list;
private Context context;
private ZhihuBannerAdapter zhihuBannerAdapter;
public ZhihuHomeAdapter(Context context) {
this.context = context;
zhihuBanberBeans = new ArrayList<>();
list = new ArrayList<>();
}
public void setData(ZhiHuHomeBean zhiHuHomeBean) {
list.clear();
zhihuBanberBeans = zhiHuHomeBean.getTop_stories();
list.add(context.getResources().getString(R.string.app_zhihu_today));
list.addAll(zhiHuHomeBean.getStories());
this.notifyDataSetChanged();
}
public void addData(ZhiHuListBean zhiHuListBean) {
list.add(zhiHuListBean.getShowData());
list.addAll(zhiHuListBean.getStories());
this.notifyItemRangeInserted(getItemCount() - 1, zhiHuListBean.getStories().size() + 1);
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == 0) {
return new HeadViewHolder(LayoutInflater.from(context).inflate(R.layout.layout_zhihu_home_head, parent, false));
} else if (viewType == 1) {
return new DateViewHolder(LayoutInflater.from(context).inflate(R.layout.item_zhihu_home_date, parent, false));
}
return new NewsViewHolder(LayoutInflater.from(context).inflate(R.layout.item_zhihu_list_item, parent, false));
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (getItemViewType(position) == 0) {
HeadViewHolder headViewHolder = (HeadViewHolder) holder;
zhihuBannerAdapter = new ZhihuBannerAdapter(context);
headViewHolder.viewpager.setAdapter(zhihuBannerAdapter);
zhihuBannerAdapter.setZhihuBanberBeans(zhihuBanberBeans);
headViewHolder.circleIndicator.setViewPager(headViewHolder.viewpager);
headViewHolder.viewpager.startAutoScroll();
} else if (getItemViewType(position) == 1) {
String date = (String) list.get(position-HEAD);
DateViewHolder dateViewHolder = (DateViewHolder) holder;
dateViewHolder.tv_data.setText(date);
} else {
NewsViewHolder newsViewHolder = (NewsViewHolder) holder;
ZhihuNewBean zhihuNewBean = (ZhihuNewBean) list.get(position - HEAD);
newsViewHolder.tv_title.setText(zhihuNewBean.getTitle());
if (zhihuNewBean.getImages() != null){
Glide.with(context).asBitmap().load(zhihuNewBean.getImages().get(0)).into(newsViewHolder.cover);
}
}
}
@Override
public int getItemCount() {
return list.size() + HEAD;
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return 0;
} else if (list.get(position - HEAD) instanceof String) {
return 1;
}
return 2;
}
public class HeadViewHolder extends RecyclerView.ViewHolder {
AutoScrollViewPager viewpager;
CircleIndicator circleIndicator;
public HeadViewHolder(View itemView) {
super(itemView);
viewpager = itemView.findViewById(R.id.viewpager);
circleIndicator = itemView.findViewById(R.id.circleIndicator);
}
}
public class DateViewHolder extends RecyclerView.ViewHolder {
TextView tv_data;
public DateViewHolder(View itemView) {
super(itemView);
tv_data = itemView.findViewById(R.id.tv_data);
}
}
public class NewsViewHolder extends RecyclerView.ViewHolder {
TextView tv_title;
ImageView cover;
public NewsViewHolder(View itemView) {
super(itemView);
tv_title = itemView.findViewById(R.id.tv_title);
cover = itemView.findViewById(R.id.cover);
}
}
}
这里的List<Object>
是将日期date
和新闻实体放在一个集合中.getItemViewType
的时候只需判断Object是否是String或者是新闻实体类.
再看如何知道滑动到哪里开始改变头部标题?
思路分析:
记录下时间的item在列表中的哪些位置,当recycleview下滑到这个位置时就改变标题.而一旦上滑显示出了上一天的数据,就变换成上一天的时间.
- 时间的item在recycleview中的哪个位置
我们的分页是就是根据时间一天天来分页的,所以只需要在加载下一天数据的之前,当前recycleview的总item数,就是下一天时间的item.
List<DateBean> integers = new ArrayList<>();
@Override
public void getData(ZhiHuHomeBean zhiHuHomeBean, boolean isrefresh) {
zhihuHomeAdapter.setData(zhiHuHomeBean);
if (isrefresh) {
swipeRefresh.setRefreshing(false);
}
integers.clear();
integers.add(new DateBean(0, getResources().getString(R.string.app_zhihu_home)));
integers.add(new DateBean(1, getResources().getString(R.string.app_zhihu_today)));
}
@Override
public void loadNext(ZhiHuListBean zhiHuListBean) {
integers.add(new DateBean(zhihuHomeAdapter.getItemCount(), zhiHuListBean.getShowData()));
zhihuHomeAdapter.addData(zhiHuListBean);
}
- recycleview上滑和下滑时
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
if (isSlideToBottom(recyclerView)) {
presenter.loadNext();
}
if (dy > 0) {
//下滑
scrollDown();
} else {
//上滑
scrollUp();
}
}
int nowPosition = -1;
private void scrollUp() {
if (nowPosition > -1&&integers.size()>nowPosition) {
if (firstVisibleItemPosition < integers.get(nowPosition).getPosition()) {
nowPosition--;
changeTitie(integers.get(nowPosition).getTitle());
}
}
}
private void scrollDown() {
if (integers.size()>nowPosition+1) {
if (integers.get(nowPosition+1).getPosition() <= firstVisibleItemPosition) {
nowPosition++;
changeTitie(integers.get(nowPosition).getTitle());
}
}
}
最后效果图: