设计一个Pull-To-Refresh-And-Load

作者: TruthKeeper | 来源:发表于2017-05-10 08:46 被阅读657次

    前言

    几乎每个App都会用到下拉刷新、上拉加载的功能,笔者在实习的时候用过第一个开源控件就是已经停止维护了的Android-PullToRefresh,然后再是江湖人称秋百万的android-Ultra-Pull-To-Refresh,在接入过程由于下拉刷新和上拉加载在设计上不处于同个层面,只支持下拉刷新,笔者当时还蠢萌蠢萌的,就开始度娘找傻瓜式简单易用的控件。
        直到找到了PullToRefreshAndLoad,哎呦我去,是真的好用,在XML节点下依次放入刷新View,内容,加载View就可以了(๑•̀ㅂ•́)و✧
        时过境迁,开发中不同产品会加入一些定制功能,例如根据下拉距离动态改变刷新头的内容,或者某个页面的下拉刷新是类似SwipeRefreshLayout的,内容不会发生偏移的,但是上拉加载不是平滑的,是要上拉拖拽式的

    基于以上种种随机事件,笔者打算动手造一个比较友好的轮子,设计思想还是基于XML中的三明治,毕竟这个轮子专用于拖拽的下拉刷新、上拉加载场景,只要对应的刷新视图和加载视图实现接口接口就行了

    直接看代码
    下载APK把玩

    先来几张下拉的效果图,多图预警,手机党注意流量,别吐槽我设计丑~~

    普通下拉:

    弹性下拉:

    层级下拉:

    抽屉下拉:

    要点

    造这个轮子的时候笔者有几个初衷:

    • ViewGroup中对触摸事件的分发进行重写
    • 内容视图兼容ListView、ScrollView、RecyclerView等任何View
    • 下拉、上拉事件导致的主体区域发生layout,距离是可控的(借鉴NestedScroll的思想)
    • 下拉刷新和上拉加载的视图布局层次可配置
    • 阻力、模式等一些配置可配置

    原理思路

    通过自定义属性或者代码设置下拉或者上拉的模式,目前有普通、弹性、层级、抽屉,原理是通过策略者模式加载不同的ViewAdapter,然后根据实现的布局层级通过bringToFront方法移形换影,判断滑动时是否消耗主体内容的滑动距离,ViewAdapter中对几种模式进行了下拉视图view和上拉视图view的layout方法调用,回弹采用值动画来调用layout方法(好像设计的不太好),当然,也可以实现自己的Adapter来实现优(lie)雅(qi)的下拉、上拉效果

    用法

    在XML中

        <com.tk.anythingpull.AnythingPullLayout
            android:id="@+id/pull_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <!--下拉刷新视图,自定义View,不写也没关系,需实现IRefresh接口-->
            <!--主体内容,可以是ScrollView,ListView等等-->
            <!--上拉加载视图,自定义View,不写也没关系,需实现ILoad接口-->
        </com.tk.anythingpull.AnythingPullLayout>
    

    自定义属性

    <declare-styleable name="AnythingPullLayout">
            <attr name="refresh_mode" format="enum">
                <!--普通下拉-->
                <enum name="pull" value="0" />
                <!--弹性下拉-->
                <enum name="flex" value="1" />
                <!--层级下拉-->
                <enum name="layer" value="2" />
                <!--抽屉下拉-->
                <enum name="dst" value="3" />
            </attr>
            <attr name="load_mode" format="enum">
                <!--普通上拉-->
                <enum name="pull" value="0" />
                <!--弹性上拉-->
                <enum name="flex" value="1" />
                <!--层级上拉-->
                <enum name="layer" value="2" />
                <!--抽屉上拉-->
                <enum name="dst" value="3" />
            </attr>
            <!--阻力,默认1.6F-->
            <attr name="refresh_resistance" format="float" />
            <attr name="load_resistance" format="float" />
            <!--回弹的时长,300ms-->
            <attr name="refresh_close_during" format="integer" />
            <attr name="load_close_during" format="integer" />
            <!--结果停留时长,750ms-->
            <attr name="refresh_result_during" format="integer" />
            <attr name="load_result_during" format="integer" />
            <!--刷新中和加载中视图是否固定-->
            <attr name="refresh_fixed" format="boolean" />
            <attr name="load_fixed" format="boolean" />
            <!--刷新和加载功能开启-->
            <attr name="refresh_enable" format="boolean" />
            <attr name="load_enable" format="boolean" />
        </declare-styleable>
    

    监听AnythingPullLayout:

        public interface OnPullListener {
            void onRefreshStart(final AnythingPullLayout pullLayout);
    
            void onLoadStart(final AnythingPullLayout pullLayout);
        }
    

    限于篇幅就只贴下拉刷新视图的实现的方法了:

        /**
         * 准备显示
         */
        void preShow();
    
        /**
         * 准备消失,回弹动画
         */
        void preDismiss();
    
        /**
         * 消失
         */
        void onDismiss();
    
        /**
         * 视图处于变化中
         *
         * @param touch
         * @param distance
         * @param status
         */
        void onPositionChange(boolean touch, int distance, @AnythingPullLayout.Status int status);
    
        /**
         * 开始刷新
         */
        void onRefreshStart();
    
        /**
         * 结果回调
         *
         * @param success
         */
        void onRefreshFinish(boolean success);
    

    项目地址

    Ps:

    • 个别模式适用的场景需要看官自己斟酌,例如抽屉模式适合内容视图已有内容的场景,而不适合一个开始没有数据的ListView;
    • 在Sample工程把布局文件中的下拉刷新视图和上拉刷新视图替换成com.tk.pullsample.log.LogRefreshViewcom.tk.pullsample.log.LogLoadView,方便查看方法的回调

    TODO,暂时不支持NestedScroll的场景

    第一次像样的coding一个框架,求大佬指点

    相关文章

      网友评论

      • 706ffab8fe0f:如何联系您呢,有个重要问题,关于刷新的 1014909632 qq
      • 听说萝莉很可爱:大神,我布局文件里面没有定义刷新和加载的view,实现了你说的两个接口,怎么刷新不了呢?
        TruthKeeper:你好,我不是大神。如果在xml中不定义刷新和加载的view的话,在第一次执行onLayout方法会计算当前的刷新、加载mode都是弹性模式,建议自定义控件里面实现接口,然后在xml中定义
      • gyymz1993:放在Viewpager中快速切换有问题,楼主有没有试过
        TruthKeeper:你好,ViewPager默认左右的滑动冲突在cancelEvent和resetEvent方法中做了处理
      • 彩色浪花之子:github上的不能运行,是不是少上传东西了,楼主看看吧
        TruthKeeper:亲,把build.gradle中用于发布到jcenter的代码注释就行了

      本文标题:设计一个Pull-To-Refresh-And-Load

      本文链接:https://www.haomeiwen.com/subject/ecjlzttx.html