PullRefreshLayout

Introduction: 下拉刷新,上拉加载,真实的回弹(overscroll)效果(媲美 qq),且大小只有 37KB(是其他主流刷新库或回弹库的 1/2,1/3,甚至是 1/4),同时,自定义 header 和 footer,可以实现任何你想的到的功能(例如:自动触发加载更多、二级刷新等)
More: Author   ReportBugs   
Tags:

由于 SDK 26.1.0 各种兼容库在 API14 以下都已经不再适配了,相关的兼容方法,兼容的 class 都已经被废弃了,甚至 MD 也只支持 API14 以上,可见 google 已经测底放弃了 android4.0 以下的设备, 所以跟随 google 大大的脚步,PullRefreshLayout 也将 MinSdk 上调到 API14.
Stable Version Latest Version Methods

ENGLISH

DEMO 下载(跑起来看一看,也许这就是你想要的效果!)

header 和状态切换演示 gif
header 和状态切换演示 gif

二级刷新
二级刷新 二级刷新

嵌套滑动
嵌套滑动 嵌套滑动 嵌套滑动

滴滴,支付宝刷新(看刷新的位置,不包括 loading 动画)详情
滴滴 支付宝

加载更多,加载完成,保持 footer
保持 footer

fun header 来自 from https://github.com/scwang90/SmartRefreshLayout
fun header

包裹 ViewPager 参考: https://github.com/genius158/PullRefreshLayout/blob/developer/app/src/main/java/com/yan/refreshloadlayouttest/testactivity/CommonActivity5.java

包裹 CoordinatorLayout 参考:https://raw.githubusercontent.com/genius158/PullRefreshLayout/master/app/src/main/java/com/yan/refreshloadlayouttest/testactivity/ScrollingActivity.java

1.概述

本库的主要特点:完美契合嵌套滑动,和与其他回弹刷新库相比更加真实的回弹效果、即使控件不可滑动,也有惯性缓冲效果(ps:如何触发——比如下拉到一定距离不放,往回滑动,即可看到效果),切换状态见 NestedActivity

1.对所有基础控件(包括,嵌套滑动例如 RecyclerView、NestedScrollView,普通的 TextView、ListView、ScrollerView、webView、LinearLayout 等)提供下拉刷新、上拉加载的支持 ,且实现无痕过度,和与其他库相比更真实的回弹效果((即使不是滑动控件)也有惯性缓冲效果)。

ps:本库没有做解耦处理(那样会增加.class,大小也会增加),目的是使库足够小,而且本库功能目的明确,不必做无用功。

gradle Stable Version

compile 'com.yan:pullrefreshlayout:(↖)'

2.说明

支持所有基础控件

//-控件设置-
    refreshLayout.autoRefresh();// 自动刷新
    refreshLayout.autoRefresh(ture);// 自动刷新,同时是否触发刷新回调
    refreshLayout.autoLoading();// 自动加载
    refreshLayout.autoLoading(ture);// 自动加载,同时是否触发加载回调

    refreshLayout.setOverScrollDampingRatio(0.35f);//  值越大 overscroll 衰减越小(如何运作:overscroll 移动偏移量*0.35) default 0.35F
    refreshLayout.setScrollInterpolator(interpolator);// 设置 scroller 的插值器
    refreshLayout.setAnimationMainInterpolator(interpolator);// 除了回弹其他所有动画的插值器 default ViscousInterpolator
    refreshLayout.setAnimationOverScrollInterpolator(interpolator);// 回弹动画的插值器 default ViscousInterpolator

    refreshLayout.setOverScrollMinDuring(int during);// 设置 overscroll 最小时间 default 60
    refreshLayout.setRefreshAnimationDuring(int refreshAnimationDuring);// 触发刷新或加载动画的执行时间 default 180
    refreshLayout.setResetAnimationDuring(int resetAnimationDuring);// 触发界面回复的动画执行时间 default 400
    refreshLayout.setDragDampingRatio(0.6f);// 阻尼系数(如何运作:移动偏移量*0.6) default 0.6F

    refreshLayout.setOverScrollAdjustValue(1f);// 用于控制 overscroll 时间 default 1f ,越大 overscroll 的时间越长
    refreshLayout.setTopOverScrollMaxTriggerOffset(300);// 用于控制顶部的 overscroll 的距离 default 65dp
    refreshLayout.setBottomOverScrollMaxTriggerOffset(300);// 用于控制底部 overscroll 的距离 default 65dp
    refreshLayout.setPullUpMaxDistance(300);// 用于控制向上移动的最大距离(大于等于 0,为 0 可模拟禁用底部回弹) 默认 控件高度
    refreshLayout.setPullDownMaxDistance(300);// 用于控制向下移动的最大距离(大于等于 0,为 0 可模拟禁用顶部回弹) 默认 控件高度

    refreshLayout.setRefreshEnable(false);// 下拉刷新是否可用 default true
    refreshLayout.setLoadMoreEnable(true);// 上拉加载是否可用 default false
    refreshLayout.setTwinkEnable(true);// 回弹是否可用 default true 
    refreshLayout.setAutoLoadingEnable(true);// 自动加载是否可用 default false

    // headerView 和 footerView 需实现 PullRefreshLayout.OnPullListener 接口调整状态
    refreshLayout.setHeaderView(headerView);// 设置 headerView
    refreshLayout.setFooterView(footerView);// 设置 footerView

    refreshLayout.isTwinkEnable();// 是否开启回弹
    refreshLayout.isRefreshEnable();// 是否开启刷新
    refreshLayout.isLoadMoreEnable();// 是否开启加载更多

    refreshLayout.isDragMoveTrendDown();// 是否处于向下移动的趋势

    refreshLayout.isDragUp();// 是否正在向上拖拽
    refreshLayout.isDragDown();// 是否正在向下拖拽
    refreshLayout.isRefreshing();// 是否正在刷新
    refreshLayout.isLoading();// 是否正在加载
    refreshLayout.isOverScrollDown();// 是否正在向下越界回弹
    refreshLayout.isOverScrollUp();// 是否正在向上越界回弹

    refreshLayout.isDragVertical();// 是否开始纵向拖拽
    refreshLayout.isDragHorizontal();// 是否开始横向拖拽
    refreshLayout.isLayoutDragMoved();// 刷新控件是否移动

    refreshLayout.isLayoutMoving();// prl 是否正在移动(也就是判断有没有动画正在执行)

    refreshLayout.isHoldingTrigger();// 是否已经触发刷新或加载
    refreshLayout.isHoldingFinishTrigger();// 是否已经触发刷新完毕或加载完毕

    refreshLayout.getMoveDistance();// 得到 refreshlayout 的移动距离
    refreshLayout.getRefreshTriggerDistance();// 得到下拉刷新的触发距离
    refreshLayout.getLoadingTriggerDistance();// 得到上拉加载的触发距离
    refreshLayout.getPullUpLimitDistance();// 得到向上拖拽最大范围(最大距离)
    refreshLayout.getPullDownLimitDistance();// 得到向下拖拽最大范围(最大距离)
    refreshLayout.getHeaderView();// 得到 headerView
    refreshLayout.getFooterView();// 得到 FooterView
    refreshLayout.getTargetView();// 得到 TargetView

    refreshLayout.setRefreshTriggerDistance(200);// 设置下拉刷新触发位置,默认为 header 的高度  
    refreshLayout.setLoadTriggerDistance(200);// 设置上拉加载触发位置,默认为 footer 的高度  
    refreshLayout.setPullUpLimitDistance(400);// 向上拖拽最大范围,默认控件高度
    refreshLayout.setPullDownLimitDistance(400);// 向下拖拽最大范围,默认控件高度

    refreshLayout.setTargetView(nestedScrollView);// 设置目标 view,可以改变滑动判断

    refreshLayout.setDispatchTouchAble(true);// 是否分发部所有事件 default true
    refreshLayout.setDispatchPullTouchAble(false);// 是否分发 pullrefreshLayout 的默认事件 default true
    refreshLayout.setDispatchChildrenEventAble(false);// 是否分发子 View 的默认事件 default true
    refreshLayout.setFooterFront(true);// 设置 footer 前置 default false
    refreshLayout.setHeaderFront(true);// 设置 header 前置 default false
    refreshLayout.setMoveWithFooter(true);// 设置 footer 跟随移动 default true
    refreshLayout.setMoveWithContent(true);// 设置直接子 view 跟随移动 default true
    refreshLayout.setMoveWithHeader(true);// 设置 header 跟随移动 default true

    refreshLayout.cancelAllAnimation();//取消所有正在执行的动画
    refreshLayout.cancelTouchEvent();//主动执行 ACTION_CANCEL 事件

    refreshLayout.moveChildren(0);// 移动子 view

    refreshLayout.requestPullDisallowInterceptTouchEvent();// touch 事件交给子 View

    refreshLayout.setOnDragIntercept(PullRefreshLayout.OnDragIntercept);// 设置滑动判定 见 BEHAIVOR2
    public static class OnDragIntercept {
        public boolean onHeaderDownIntercept() {// header 下拉之前的拦截事件
            return true;// true 将拦截子 view 的滑动
        }
        public boolean onFooterUpIntercept() {// footer 上拉之前的拦截事件
            return true;// true 将拦截子 view 的滑动
        }
    }

    /**
    * 设置 header 或者 footer 的的出现方式,默认 8 种方式
    * FOLLOW,FOLLOW_PLACEHOLDER, PLACEHOLDER_FOLLOW
    * , FOLLOW_CENTER, PLACEHOLDER_CENTER
    * , CENTER, CENTER_FOLLOW
    * , PLACEHOLDER
    */
    refreshLayout.setRefreshShowGravity(RefreshShowHelper.CENTER,RefreshShowHelper.CENTER);
    refreshLayout.setHeaderShowGravity(RefreshShowHelper.CENTER)// header 拽出方式
    refreshLayout.setFooterShowGravity(RefreshShowHelper.CENTER)// footer 拽出方式
    // PullRefreshLayout.OnPullListener
        public interface OnPullListener {
            // 刷新或加载过程中位置相刷新或加载触发位置的百分比,时刻调用
            void onPullChange(float percent);
            void onPullReset();// 数据重置调用
            void onPullHoldTrigger();// 拖拽超过触发位置调用
            void onPullHoldUnTrigger();// 拖拽回到触发位置之前调用
            void onPullHolding(); // 正在刷新
            void onPullFinish();// 刷新完成
        }

    <!-- xml setting -->     
    <com.yan.pullrefreshlayout.PullRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
        app:prl_autoLoadingEnable="false"
        app:prl_dragDampingRatio="0.6"
        app:prl_resetAnimationDuring="400"
        app:prl_refreshAnimationDuring="180"
        app:prl_footerShowGravity="follow"
        app:prl_footerViewId="@layout/header_or_footer"
        app:prl_headerViewId="@layout/header_or_footer"
        app:prl_headerClass="com.yan.refreshloadlayouttest.widget.PlaceHolderHeader"
        app:prl_footerClass="com.yan.refreshloadlayouttest.widget.PlaceHolderHeader"
        app:prl_headerShowGravity="statePlaceholder"
        app:prl_loadMoreEnable="true"
        app:prl_loadTriggerDistance="70dp"
        app:prl_overScrollDampingRatio="0.2"
        app:prl_topOverScrollMaxTriggerOffset="65dp"
        app:prl_bottomOverScrollMaxTriggerOffset="70dp"
        app:prl_pullDownMaxDistance="150dp"
        app:prl_pullUpMaxDistance="250dp"
        app:prl_headerFront="true"
        app:prl_footerFront="true"
        app:prl_targetId="@+id/recyclerView"
        app:prl_refreshEnable="true"
        app:prl_refreshTriggerDistance="90dp"
        app:prl_overScrollAdjustValue="1"
        app:prl_twinkEnable="true">     

        <!-- 通过以下例子,你可以轻易实现 recyclerView(任何 View)的 header,和数据错误、网络错误等的状态切换--> 
        <com.yan.pullrefreshlayout.PullRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/refreshLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:prl_targetId="@+id/recyclerView">

            <!-- 包装层,实现了嵌套滑动的功能,也可以是普通的 FrameLayout(实现机制不同) -->
            <com.yan.refreshloadlayouttest.widget.NestedFrameLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <!-- 数据添装 -->
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@id/recyclerView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="#f1f1f1"
                    android:overScrollMode="never" />

                <!-- header -->
                <androidx.cardview.widget.CardView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="46dp"
                        android:gravity="center"
                        android:text="header"
                        android:textSize="18sp" />
                </androidx.cardview.widget.CardView>

                <!-- 状态显示界面 -->
                <TextView
                    android:id="@+id/no_data"
                    android:background="#ffffff"
                    android:gravity="center"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:textColor="#212121"
                    android:textSize="20sp"
                    android:text="no data click to try again"  />
            </com.yan.refreshloadlayouttest.widget.NestedFrameLayout>

        </com.yan.pullrefreshlayout.PullRefreshLayout>

3.版本说明

version:1.7.3 : 事件切换优化
version:1.7.4 : 一直处于触摸状态下的,执行 setTargetView()方法,增加 cancel 掉事件
version:1.7.5 : 修复事件状态错误问题
version:1.7.8 : 调整由于 onStartNestedScroll()先于 onNestedPreFling()执行的二次 handleAction
version:1.8.5 : 优化 flingBack 效果


version:2.0.0 : 新增 SwipeRefreshLayout 效果,优化
version:2.0.2 : 新增 viewpager 实例
version:2.0.3 : dragState 状态调整

version:2.0.5 : 使用粘性插值器
version:2.0.7 : 调整回弹插值器为线性插值器,回弹效果基本与 qq 一致
version:2.0.9 : 多点触控调整
version:2.0.10 : insert methods getHeaderView(),getFooterView),getTargetView()
version:2.1.0 : detail adjust
version:2.2.1 :add method autoRefresh(int toRefreshDistance),autoLoading(int toLoadDistance)
version:2.2.2 :修复回调时机错误问题
version:2.2.4 :剔除反射部分,恢复 header 划出的原本处理
version:2.2.5 :增加 isTargetScrollUpAble 和 isTargetScrollDownAble 拦截监听
version:2.2.6 :fix issue https://github.com/genius158/PullRefreshLayout/issues/25
version:2.2.7 :增强滑动体验
version:3.x :androidx
version:3.0.1 :主动分发的事件,主动回收

4.问题 FAQ(对于本库的使用有问题,都可以在 github 上提 issue,本人重度 github 控,一天最最少半小时泡在 github 上)

1.VLayout 设置悬浮后,不可下拉(问题可见https://github.com/alibaba/vlayout/blob/master/docs/VLayoutFAQ.md)
官方描述如下:
下拉刷新,有很多框架是通过判断 RecyclerView 的第一个 view 的 top 是否为 0 来触发下拉动作。VLayout 里在处理背景、悬浮态的时候加入了一些对 LayoutManager 不可见的 View,但又真实存在与 RecyclerView 的视图树里,建议使用 layoutManager.getChildAt(0) 来获取第一个 view。

解决办法:可复写刷新判断

    @Override
    public boolean isTargetScrollUpAble() {
          ...



友情提示: ( 来自pjh520的的提点 )如果你使用了 scrollView,它的直接子 View 请不要设置纵向的 margin 会影响滑动判断,这点也是刷新库的通病。。。

5.demo 用到的库

loading 动画
AVLoadingIndicatorView(https://github.com/81813780/AVLoadingIndicatorView)

LICENSE

Copyright 2017 yan

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apps
About Me
GitHub: Trinea
Facebook: Dev Tools