文档章节

PullToRefresh的学习研究(一)

j
 jscoolstar
发布于 2015/06/16 15:45
字数 2610
阅读 121
收藏 1

其实以前在看和学习源码的时候都是在源码上做关键的备注,后面看的时候能慢慢回忆起来,但是对比较复杂的逻辑,光靠备注还是不行,即使能回忆起来,也是十分耗费时间的。所以感觉还是要把学习时的逻辑、感受都记录一下,这样在很久以后再看的时候,能迅速记起。另外写成博客,势必有人会来看,也许是新手,那么很荣幸能帮助大家。也许是大神,那么很庆幸您的到来,留下建议和教诲。我想博客就应该是这样需要交流的,不能完全只是记录。


好了,作为处女的开篇博客,难免开局要啰嗦一下,另外。。。我不太会写博客,到这会我还没找到怎么上传zip源码包,所以大家直接搜PullToRefresh来搜索下载吧。。。还有就是,我现在是看了一天但并没看完,说起来,我自己的技术也很薄弱,写博客并不是以师者的姿态来写的,所以望大家不要乱喷,而打消个人的积极性。写的不好不清楚大家可以在评论里继续讨论,我也可以按大家的指点继续完善。本来博客写出来更多的还是为了自己的学习和成长,顺便能帮到人那是最好的了。并不是为了喷子服务的:)


功能特点与大体原理:

1、能实现listview/gridview/scrollview/webview../hscrollview/viewpager等等的下(左)拉刷新、上(右)拉加载更多。

        目前我看到的源码,是能支持多种view的刷新的。甚至你可以参考代码自定义一个Linearlayout都可以进行同样的UI体验            它支持纵向和横向。  

2、多种模式:可以只使用下拉或上拉或both或禁用

3、有一个特效类似微信朋友圈的刷新,即下拉的时候有个小图标会按下拉的程度慢慢旋转,松手时会快速旋转,加载完成后,消失。我觉得可以叫它“发条效果”。。。呵呵,下拉的时候就是在上紧,松手就呼啦啦的转

4、目前得到的粗粗的原理:。。。。继承LinearLayout,基类是abstract的,listview为例,有一个PullToRefreshListview继承,并通过abstract getPullToRefreshScrollDirection()方式,来定位自己是 横向的还是纵向的,这里就是纵向了。原理就是add了一个listview(index是-1),然后add了一个header(index是0)和一个footer(index是-1);然后通过padding属性,定位到listview全部显示,header和footer上下隐藏。定位好后的操作,比如下拉等等,就是主要通过scrollto()来实现,松手弹回也是利用一个加速器和一个runnable来多次执行scrollto来实现。(这是当前看到的原理,我还要继续读继续写,后面如果发现错了再来改过,如果有大神熟悉它的话,发现我的错误,还望不吝赐教)。


源码解读开始吧

1、构造方法:

    public PullToRefreshBase(Context context) {
        super(context);
        init(context, null);
    }

    public PullToRefreshBase(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    主要就是init(context, attrs);好处就是attrs传了进去,这里是可以借鉴的地方(高手看到我这么说,不要嘘我,很多新手都注意不到这里的)。

  init中第一步就是取方向:switch (getPullToRefreshScrollDirection()) {
        case HORIZONTAL:
            setOrientation(LinearLayout.HORIZONTAL);
            break;
        case VERTICAL:
        default:
            setOrientation(LinearLayout.VERTICAL);
            break;
        }

getPullToRefreshScrollDirection()是由子类来实现的,也就是说如果要实现说的可以让listview/gridview等等实现下拉,要分别实现对应的子类,感觉上是有点累赘,但是方便进行特定化的实现。有些类似的组件是你中间放啥都可以,尤其有些原理都类似的组件,他直接在一个类里,去获得你中间放的是adapterview还是scrollview等等,然后touchevent里就开始了各种判断。并不是说那样的源码不好,只是单从我来看,那么长的源码,一下子我就恐惧了。

setGravity(Gravity.CENTER);、这里我没读懂,为啥一定要居中。在这里要强调另外一点,他重写了addview()

@Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (DEBUG) {
            Log.d(LOG_TAG, "addView: " + child.getClass().getSimpleName());
        }

        final T refreshableView = getRefreshableView();

        if (refreshableView instanceof ViewGroup) {
            ((ViewGroup) refreshableView).addView(child, index, params);
        } else {
            throw new UnsupportedOperationException(
                    "Refreshable View is not a ViewGroup so can't addView");
        }
    }可以看到当调用自定义类去addview的时候,其实他会往refreshableView上去添加,如果你不是ViewGroup类型的,是无法添加的


ViewConfiguration config = ViewConfiguration.get(context);
        mTouchSlop = config.getScaledTouchSlop();// 获取最小的滑动距离,手指要移动这个距离或以上才会被认为发生了滑动



TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.PullToRefresh);,拿到属性之后,基类自己记录了一些必要属性,然后感觉下面就是经典了。

mRefreshableView = createRefreshableView(context, attrs);
        addRefreshableView(context, mRefreshableView);  createRfreshableView(context,attrs)这个是要由子类实现的方法,即子类来决定自己是实现listview还是gridview等等。另外这里传入了attrs。也就是说,虽然自己继承的是LinearLayout,但是你的子类可以起名为xxxxxxListView,然后xml中就把它当作listview,可以设置drawableCache等Listview的属性,然后子类的createRfreshableView就可以拿到attrs来构建自己的listview了,然后再实现什么setAdater(){mListView.setAdapter()}等listview的常用方法,通过自己这个桥梁直接调用listview的对应方法。这样使用者就可以完全当作listview来使用了。基类提供了@Override
    public final T getRefreshableView() {
        return mRefreshableView;
    },使用者也可以直接get到之后,然后直接转成对应的view,就可以直接进行操作了,比如这套代码中的xxxxxlistview虽然有setAdapter,onItemClickListerner等常用设置,但是没有addHeader,addFooter等等。毕竟这个例子中的下拉后的顶部hearder并不是真正的listview的header,当然也没有验证加了header之后能不能行,后面还要继续看,标红一下回头看。

    接下来:mHeaderLayout = createLoadingLayout(context, Mode.PULL_FROM_START, a);
        mFooterLayout = createLoadingLayout(context, Mode.PULL_FROM_END, a);、、createLoadingLayout这里是根据xml配置rotatol或flip,即常见的箭头反转动画或者是微信拿样的旋转发条动画。例子中是写死了两种LoadingLayout,xml只能配置1个,目前不知道能否在子类 重写,毕竟基类中只是操作了mHeaderLayout的几种刷新周期中的属性,比如onFresh,over啊等等,然后比如RotatotLoadingLayout会在onFresh啊等等周期方法里去setLable以展示信息,或者旋转或反转动画,所以应该是可以自定义继承LoadingLayout的,这里也标红,后面再看。


        init的后面执行了handleStyledAttributes(a);//这里就是说基类把一些必要的属性都用掉了,需要额外使用的属性,子类通过这个abstract方法去解析和实现
        a.recycle();

        最后执行了updateUIForMode()方法,内部

        if (mMode.showHeaderLoadingLayout()) {//这个方法其实就是一开始说的配置的model,如果是fromstart或者是both,就返回true,代码翻译成中文就是,如果用户设置的加载模式是顶部下拉刷新,或者both(上拉下拉都可以),那么就返回true就会add这个view
            addViewInternal(mHeaderLayout, 0, lp);
        }

     add headerlayout和footlayout之后,执行了refreshLoadingViewsSize();这里面就是计算header(一说header,footer就包含了,后面只用header说话,也只用listview说话,后面研究子类的时候再单独用相应的子view来说)的高度,以及padding,这里就是最亮的那个点了。这里在赘述下原理,即listview和自己(LinearLayout)一样高,header只有0.6倍的高度,然后自己的paddingtop是-0.6被高度。正好把header顶出自己的显示区域,然后根据子类的临界值判断,使用srcollto方法来让header滚动出来或滑动回去。(这里也是我担心的listview的真正header的问题,因源码中并未涉及到,所以在判断临界值的时候,有head和无head是一样的情况还是不通的情况呢?后面再看)


protected final void refreshLoadingViewsSize() {
        final int maximumPullScroll = (int) (getMaximumPullScroll() * 1.2f);

        int pLeft = getPaddingLeft();
        int pTop = getPaddingTop();
        int pRight = getPaddingRight();
        int pBottom = getPaddingBottom();

        switch (getPullToRefreshScrollDirection()) {
        case HORIZONTAL:
            if (mMode.showHeaderLoadingLayout()) {
                mHeaderLayout.setWidth(maximumPullScroll);
                pLeft = -maximumPullScroll;
            } else {
                pLeft = 0;
            }

            if (mMode.showFooterLoadingLayout()) {
                mFooterLayout.setWidth(maximumPullScroll);
                pRight = -maximumPullScroll;
            } else {
                pRight = 0;
            }
            break;

        case VERTICAL:
            if (mMode.showHeaderLoadingLayout()) {
                mHeaderLayout.setHeight(maximumPullScroll);
                pTop = -maximumPullScroll;
            } else {
                pTop = 0;
            }

            if (mMode.showFooterLoadingLayout()) {
                mFooterLayout.setHeight(maximumPullScroll);
                pBottom = -maximumPullScroll;
            } else {
                pBottom = 0;
            }
            break;
        }

        if (DEBUG) {
            Log.d(LOG_TAG, String.format(
                    "Setting Padding. L: %d, T: %d, R: %d, B: %d", pLeft, pTop,
                    pRight, pBottom));
        }
        setPadding(pLeft, pTop, pRight, pBottom);


与其相映的一个方法protected final void refreshRefreshableViewSize(int width, int height) {
        // We need to set the Height of the Refreshable View to the same as
        // this layout
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mRefreshableViewWrapper
                .getLayoutParams();

        switch (getPullToRefreshScrollDirection()) {
        case HORIZONTAL:
            if (lp.width != width) {
                lp.width = width;
                mRefreshableViewWrapper.requestLayout();
            }
            break;
        case VERTICAL:
            if (lp.height != height) {
                lp.height = height;
                mRefreshableViewWrapper.requestLayout();
            }
            break;
        }
    }这个方法就是把refreshview整的和参数的w/h相等。额源码的refreshableview并不是加载自己上的,而是加在了mRefreshableViewWrapper(FrameLayout)之上,又把mRefreshableViewWrapper加在了自己上,然后是fillparent加上去的,也就是说以后mRefreshableViewWrapper有多大,refreshview就有多大。而这个方法在基类只有一个地方那个使用了:@Override
    protected final void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (DEBUG) {
            Log.d(LOG_TAG, String.format("onSizeChanged. W: %d, H: %d", w, h));
        }

        super.onSizeChanged(w, h, oldw, oldh);

        // We need to update the header/footer when our size changes
        refreshLoadingViewsSize();

        // Update the Refreshable View layout
        refreshRefreshableViewSize(w, h);

        /**
         * As we're currently in a Layout Pass, we need to schedule another one
         * to layout any changes we've made here
         */
        post(new Runnable() {
            @Override
            public void run() {
                requestLayout();
            }
        });
    }

其实代码都是人写的,不同的人读就可能挑刺,比如我觉得为啥  refreshLoadingViewsSize();不用传w,h而是方法内部用getWidth等拿到之后再乘0.6(/2*1.2),而refreshRefreshableViewSize(w, h);方法就要传w,h进去呢?



到这里,基本上用户在xml配置好之后,源码的第一步解析就到此为止了。该解析的都解析了,mFreshableView是啥东西也知道,也add了。接下来就是onTouch事件了,也是实现的主逻辑了,接下来就边看边研究边写了。另外本篇的疑问,大家也都要有印象,后面会慢慢的填坑。。



© 著作权归作者所有

共有 人打赏支持
j
粉丝 0
博文 3
码字总数 6889
作品 0
海淀
程序员
私信 提问
Android-PullToRefresh使用

接上篇,很多人看到有好源码,但是在使用时碰到了问题。在此简单介绍一下,希望能够对那些不会的童鞋们有所帮助。 首先下载源码,源码地址:https://github.com/chrisbanes/Android-PullToR...

崔同亮
2013/10/23
0
0
腾讯 AI Lab 主任张潼博士:机器学习里的优化问题

雷锋网 AI 科技评论按,日前,在由上海财经大学交叉科学研究院(RIIS)主办,杉数科技有限公司协办的「现代运筹学发展讨论会」上,腾讯 AI Lab(腾讯人工智能实验室)主任张潼博士发表了精彩...

思颖
01/10
0
0
学习东西总结:

一.JAVA 基础 1、JAVA并发编程 2.JAVA NIO系列 3.JVM深入了解 4.JAVA性能优化相关研究 5.JAVA7,8新特性 二、数据库 1.数据库优化研究 2.MYSQL高可用性研究 3.MYSQL源码研究 三、网络 1.HTTP...

QH_C
2016/03/25
5
0
智能卡开发的相关总结

1.EMV技术学习和研究(一)开篇(http://blog.csdn.net/xuture/article/details/9208259) 2.EMV技术学习和研究(二)应用选择(http://blog.csdn.net/xuture/article/details/9250067) 3.EMV技......

IT追寻者
2016/09/08
4
0
跟风Google Brain,Facebook AI研究机构启动见习项目

2015年,Google Brain公布了其帮助机器学习研究者进阶的见习项目,研究内容灵活、薪资福利又高、发展机会应有尽有,瞬间吸引了大量的申请者,其中甚至还有Node.js之父Ryan Dahl,AI科技大本营...

dqcfkyqdxym3f8rb0
2017/12/06
0
0

没有更多内容

加载失败,请刷新页面

加载更多

区块链安全 - 以太坊短地址攻击

1 基础知识 EVM虚拟机在解析合约的字节码时,依赖的是ABI的定义,从而去识别各个字段位于字节码的什么地方。关于ABI,可以阅读这个文档: https://github.com/ethereum/wiki/wiki/Ethereum-C...

HiBlock
10分钟前
0
0
自定义函数及内部函数

变量的作用域 局部变量 global $Global及其他超全局数组 静态变量 仅初始化赋值 保留于内存直到response才销毁 global和static变量的区别 global:局部变量全局话 static:定义静态局部变量 函...

关元
11分钟前
0
0

中国龙-扬科
23分钟前
1
0
python包

https://www.lfd.uci.edu/~gohlke/pythonlibs/

陆朋
33分钟前
1
0
一文弄懂“分布式锁”,一直以来你的选择依据正确吗?

本文主要会关注的问题是“分布式锁”的问题。 多线程情况下对共享资源的操作需要加锁,避免数据被写乱,在分布式系统中,这个问题也是存在的,此时就需要一个分布式锁服务。 常见的分布式锁实...

Java干货分享
41分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部