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

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

我们需实现的效果有:

  • 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());
            }
        }

    }

最后效果图:


最后效果.gif

项目地址

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容