仿知乎日报首页滑动上拉效果

首先看下原版知乎日报首页效果
知乎日报首页.gif

我们需实现的效果有:

  • 1.下拉刷新数据
  • 2.滑动到底部加载下一页数据
  • 3.随着列表页的滑动,头部的标题也发生变化.

好,有了需求,我们一步一步实现.重点是第三个效果.

1.首先,确定页面的布局

从上往下:toolBar,下拉刷新控件swipeRefresh,还是数据填充的列表页recycleView.
笔者因为个人项目规划的原因,把toolbar放在了Mainactivity中.
所以布局的代码如下:
activity_main



    
        

        
        

        
    

    

    

fragment_zhihu_home


    

    

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:
  • URL: https://news-at.zhihu.com/api/4/news/latest
  • 响应实例:
      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.过往消息
  • URL: https://news-at.zhihu.com/api/4/news/before/20131119

若果需要查询 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 {
    public static final int HEAD = 1;
    private List zhihuBanberBeans;
    private List 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是将日期date和新闻实体放在一个集合中.getItemViewType的时候只需判断Object是否是String或者是新闻实体类.

再看如何知道滑动到哪里开始改变头部标题?

思路分析:
记录下时间的item在列表中的哪些位置,当recycleview下滑到这个位置时就改变标题.而一旦上滑显示出了上一天的数据,就变换成上一天的时间.

  • 时间的item在recycleview中的哪个位置
    我们的分页是就是根据时间一天天来分页的,所以只需要在加载下一天数据的之前,当前recycleview的总item数,就是下一天时间的item.
 List 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());
            }
        }

    }

最后效果图:


最后效果.gif

项目地址

你可能感兴趣的:(仿知乎日报首页滑动上拉效果)