上一篇文章介绍了高仿京东的沉浸式状态栏,可是跟京东首页的头部轮播图相比,依然有三处缺憾:
1、京东的头部Banner上方,除了有悬浮着的状态栏,状态栏下面还有一行悬浮工具栏,内嵌扫一扫图标、搜索框,以及消息图标;
2、把整个页面往上拉,状态栏的背景色从透明变为深灰,同时工具栏的背景也从透明变为白色;
3、页面下拉到顶后,继续下拉会拉出带有“下拉刷新”字样的布局,此时松手则会触发页面的刷新动作;
上面第一点的状态栏和工具栏悬浮效果,都有对应的解决办法;第二点的状态栏和工具栏背景变更,也存在可行的解决方案。倒是第三点的下拉刷新,以及第二点的上拉监听,却不容易实现。
虽然Android提供了专门的下拉刷新布局SwipeRefreshLayout,但它并没有页面随手势下滚的效果。一些第三方的开源库如PullToRefresh、SmartRefreshLayout固然能让整体页面下滑,可是顶部的下拉布局很难个性化定制,至于状态栏、工具栏的背景色修改更是三不管。因此若想呈现完全仿照京东的下拉刷新特效,只能由开发者编写一个自定义的布局控件了。
自定义的下拉刷新布局,首先要能够区分是页面的正常下滚,还是拉伸头部要求刷新。二者之间的区别很简单,直觉上看就是判断当前页面是否拉到顶了。倘若还没拉到顶,继续下拉动作属于正常的页面滚动;倘若已经拉到顶了,继续下拉动作才会拉出头部提示刷新。所以此处得捕捉页面滚动到顶部的事件,相对应的则是页面滚动到底部的事件。鉴于App首页基本采用滚动视图ScrollView实现页面滚动功能,故而该问题就变成了如何监听该视图滚到顶部或者滚到底部。正好ScrollView提供了滚动行为的变化方法onScrollChanged,通过重写该方法即可判断是否到达顶部或底部,重写后的代码片段如下所示:
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
boolean isScrolledToTop;
boolean isScrolledToBottom;
if (getScrollY() == 0) {
// 下拉滚动到顶部
isScrolledToTop = true;
isScrolledToBottom = false;
} else if (getScrollY() + getHeight() - getPaddingTop() - getPaddingBottom() == getChildAt(0).getHeight()) {
// 上拉滚动到底部
isScrolledToBottom = true;
isScrolledToTop = false;
} else {
// 未拉到顶部,也未拉到底部
isScrolledToTop = false;
isScrolledToBottom = false;
}
if (mScrollListener != null) {
if (isScrolledToTop) {
// 触发下拉到顶部的事件
mScrollListener.onScrolledToTop();
} else if (isScrolledToBottom) {
// 触发上拉到底部的事件
mScrollListener.onScrolledToBottom();
}
}
}
private ScrollListener mScrollListener;
// 设置滚动监听器的实例
public void setScrollListener(ScrollListener listener) {
mScrollListener = listener;
}
// 定义一个滚动监听器,用于捕捉到达顶部和到达底部的事件
public interface ScrollListener {
void onScrolledToBottom();
void onScrolledToTop();
}
如此改造一番,只要页面Activity设置滚动视图的滚动监听器,就能经由onScrolledToTop方法判断当前页面是否拉到顶了。既然可以知晓到顶与否,同步变更状态栏和工具栏的背景色也是可行的了。下面是演示页面拉到顶部附件的两种效果图,其中左图为上拉页面使之整体上滑,此时状态栏的背景变灰、工具栏的背景变白;右图为下拉页面使之接近顶部,此时状态栏和工具栏的背景均恢复透明。
然而成功监听页面是否到达顶部或底部,仅仅解决了状态栏和工具栏的变色问题。因为页面到顶时继续下拉,ScrollView要怎么处理?一方面是整个页面已经拉到顶了,造成ScrollView已经无可再拉;另一方面,用户在京东首页看到的下拉头部,其实并不属于ScrollView管辖,即使ScrollView想拉这个头部兄弟一把,也只能有心无力。不管ScrollView是惊慌失措,还是不知所措,恰恰说明它是真正的束手无策了,为此还要一个和事佬来摆平下拉布局和滚动视图之间的纠纷。
这个和事佬必须是下拉布局和滚动视图的上级布局,考虑到下拉布局在上,而滚动视图在下,故它俩的上级布局继承线性布局LinearLayout比较合适。新的上层视图需要完成以下三项任务:
一、在下层视图的最前面自动添加一个下拉刷新头部,保证该下拉头部位于整个页面的最上方;
二、给前面自定义的滚动视图注册滚动监听器和触摸监听器,其中滚动监听器用于处理到达顶部/底部的事件,触摸监听器用于处理下拉过程中的持续位移。
三、重写触摸监听器接口需要实现的onTouch函数,这个是重中之重,因为该函数包含了所有的手势下拉跟踪处理。既要准确响应正常的下拉手势,也要避免误操作不属于下拉的手势,比如下面几种情况就得统筹考虑:
1、水平方向的左右滑动,不做额外处理;
2、垂直方向的向上拉动,不做额外处理;
3、下拉的时候,如果尚未拉到页面顶部,也不做额外处理;
4、拉到顶之后继续下拉,则隐藏工具栏的同时,还要让下拉头部跟着往下滑动;
5、下拉刷新过程中松开手势,判断下拉滚动的距离,距离太短则直接缩回头部、不进行页面刷新;只有距离足够长,才能触发页面刷新动作,等待刷新完毕再缩回头部。
现在有了新定义的下拉上层布局,搭配自定义的滚动视图,就能很方便地实现高仿京东首页的下拉刷新效果了。具体实现的首页布局模板如下所示:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<!-- PullDownRefreshLayout是自定义的下拉上层布局 -->
<com.example.event.widget.PullDownRefreshLayout
android:id="@+id/pdrl_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- PullDownScrollView是自定义的滚动视图 -->
<com.example.event.widget.PullDownScrollView
android:id="@+id/pdsv_main"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 此处放具体页面的布局内容 -->
</LinearLayout>
</com.example.event.widget.PullDownScrollView>
</com.example.event.widget.PullDownRefreshLayout>
<!-- title_drag.xml是带搜索框的工具栏布局 -->
<include layout="@layout/title_drag" />
</RelativeLayout>
以上布局模板用到的PullDownRefreshLayout和PullDownScrollView,因为代码量较多,这里就不贴出来,有需要的朋友请留下邮箱,我单独发过去。运行改造后的测试App,下拉刷新的效果见下列组图,其中左图为正在下拉时的截图,右图为松开下拉、开始刷新之时的截图。