关于Android 下拉刷新,上拉加载更多数据功能的详细解析

之前一直都是用别的的组件做的这个刷新数据功能,但是后面出现了需求的变更,不太好改。所以就还是自己去写一个的好,到时想怎么改就怎么改。

关于这个功能,第一个想到的需要了解的两样东西,第一个就是android中view的初始化流程,第二个就是view的事件。





从官网得知,通常如果要去自定义一个view可以去实现这些方法,并对他们做一些处理就差不多可以了。

首先,建立一个布局文件。样式显示将为是这个样子


有颜色的都是自定义view,分别为GreenLayoutView   BlueLayoutView   OrangeView ,GreenLayoutView,BlueLayoutView是继承于Linearlayout的View,  GreenLayoutView是最上层的GroupView包裹所有View,而BlueLayoutView包裹OrangeView,RedView在BlueLayoutView下方。




下面就先从view的初始化流程入手(新建一个类继承于view,并在view中复写他们的这些方法)。

一:view的初始化流程及其相关方法

在view初始化时会依次执行   

构造->onFinishInflate->onAttachedToWindow->onWindowVisibilityChanged->onMeasure(可能会多次调用)->onSizeChanged->onLayout(可能会多次调用)->onDraw(可能会多次调用)->onWindowVisibilityChanged->onDetachedFromWindow




从字面上就可以理解,一个view在初始化的时候,它会先走它自己的构造方法-然后如果这个View是通过一个xml布局文件来表示的话(不是xml文件,就比如说View view=new View(context),这个是不会触发onFinishInflate方法的)在加载完xml文件后触发finishInflate方法,然后当在window中显示。

这里要着重关注view的onMeasure和onLayout方法,因为这两个方法注定了view的大小和它的位置,而onDraw呢当然就是view会成为什么样子的了。


当一个View中包含了子View它是这样触发的,只要记住一点,方法都是先执行把view的放进window中再执行初始化view参数值。把view放进activity的window中是按层放的,也就是先放父控件再放子控制,由xml布局文件决定。而初始化view都是按层给只不过是相反,由最上层到最下层,由子到父,例如onMeasure,onLayout等方法,比如现在GreenLayoutView包含的是BlueLayoutView,那么就是onMeasure(BlueLayoutView)->onMeasure(GreenLayoutView)->onLayout(BlueLayoutView)->onLayout(GreenLayoutView)。依次类推


onMeasure方法是一个view去测量到他实际的长跟宽,当执行到这个方法的时候,你就可以拿到他的宽长度了,如view.getWidth,而onLayout是要把这个view放在哪里,他们可能会多次被调用,我测试过的,如何这个view是继承于LinearLayout会调用两次,而继承于RelativeLayout会触发3次。现在需求是上拉加载,下拉刷新,这里只涉及到大小的改变的位置的改变,所以就把别的view的别的方法去掉只留下onMeasure跟onLayout方法。

另外还有点值得注意,当一个view是在activity中显示的时候与activity生命周期相结合会这样执行:

onCreate(activity)->view构造(view)->onFinishInflate(view)->onStart(activity)->onResume(activity)->onAttachedToWindow(activity)->onAttachedToWindow(view)-.从这里开始执行的都是view的方法从onWindowVisibilityChanged到onDraw.。


所以从这里知道,即使你通过findviewbyID可以拿到显示在这个activity中的view实例你也无法在activity的初始化周期中拿到view的大小。如onCreate方法中view.getWidth就是0值,在onResume得到的view.getWidth也是0值。因为view是在activity已经attachtowindow之后才去执行view的attachtowindow,onMeasure等方法的,当然就拿不到了。


二:view的事件传递

虽然官网说去实现上图的方法确实是可以达到 ,但是为了能更清晰的去说明view的事件传递机制,这里添加两个事件方法dispatchTouchEvent,onInterceptTouchEvent.

并且复写他们的onTouchEvent   dispatchTouchEvent   onInterceptTouchEvent(只有继承于ViewGroup才有的方法,而继承于View的不用复写这个方法)三个方法。

首先先点击GreenLayoutView--打印如下:


点击BlueViewLayout--打印如下:




点击RedView--打印如下:



点击OrangeView打印如下:




从上,可知:

onInterceptTouchEvent是最先触发的一方法,并且此方法只有继承于ViewGroup的view才有的方法,无论点击的是哪个View都会最先触发根View的onInterceptTouchEvent方法。如果把布局看成是层结构那么最上层就是OrangeView,往下是BlueLayoutView和RedView(它们是同一层,并且互相不重叠),最下一层是GreenLayoutView。

事件的走向是onInterceptTouch->onTouch->dispatchTouch.

当点击了GreenLayoutView时,触发onInterceptTouchEvent,然后会去判断这个触摸点是否有碰到GreenLayoutView的子view,如果没有,就会触发本身的onTouch-然后再触发dispatchTouch。而如果有子view被点击到的话,例如我点击了BlueLayoutView,则触发GreenLayoutView的onInterceptTouch之后就会先把onInterceptTouch传递给触摸到的子view,依此类推,如果还有子view(如点击了OrangeView)又先继续传递,直到没有碰到的子view了。就在最后接收的这个子view上执行onTouch,dispatchTouch,子view执行完后,再返回上一层执行onTouch,dispatchTouch.

所以说,onInterceptTouch是由下往上传递的,而onTouch是由上住下传递的。

这里只考虑了默认情况下,发现,这三个事件方法都是有一个boolean的返回值的,并且默认都是返回false,这里的意思是,当收到事件的时候都不去处理,如果打印传递的事件我们发现,他是key_down,也就是接收到的了down事件而之后的move,up都没有,很正常,因为你连down都不处理,那他就不会把剩下的事件传给你了。如果我们想要处理怎么办,很简单,只要在你想处理的事件那里返回true就行了。

onInterceptTouch跟onTouch比较特别,单单继承View是没有onInterceptTouch方法的,这个方法是为了给拥有装载view功能的ViewGroup使用,以告知我接收到了事件。要注意onInterceptTouch是接收事件,注重在接收,而onTouch是处理事件。文档也说了,如果你在onInterceptTouch返回了true,那么你应该在本类的onTouch方法也返回true.下面在BlueLayoutView的onInterceptTouch和onTouch返回true,再次点击BlueLayoutView,结果是这样(如果你返回true就直接返回一个true,不要再调用类似super.onInterceptTouchEvent(ev)这个方法了,这样就混淆程序了,你都处理了你还抛给父类做什么,大家可以试一试):


这时你会发现,BlueLayoutView不仅处理了Down,还处理了move以及UP,,移动手指就会看见打印很多。当BlueLayoutView处理完一个事件后,还是会把它向父一级传送的。因此,GreenLayoutView也可以接收到down,move,up事件。

现在点击一个OrangeView,结果是这样的:


发现,他跟只点击BlueLayoutView的结果是一样的,这就说明了,确实是拦截到事件了,并且处理了,不给它再向上一级传递了也就是传递给OrangeView。


为了表现onInterceptTouch的作用,现在把onInterceptTouch还原为return super.onInterceptTouchEvent(ev);而onTouch依然返回true。这表示,当事件来时,一方面我会把接收到的事件传递给子View如果子view是在这个触摸点上的话,另一方面,我也用自身的onTouch处理。

点击OrangeView后的输入如下:



这时候发现,BlueLayoutView和OrangeView都接收到了down事件,但只有BlueLayoutView对move,和up有打印,这就说明了BlueLayout处理了onTouch,而OrangeView没有,但是他接收到了一个事件也就是down,因为BlueLayoutView的interceptTouch返回false不拦截继续发送给它了。


onTouchEvent好理解,那dispatchTouchEvent呢,其实dispatch就是分发的意思,如果只是在BlueLayoutView的dispatchTouch事件返回true,点击OrangeView看一下是什么结果:



我们看到OrangeView的所有事件并没有被触发,而且BlueLayoutView的onTouch没有触发,而且GreenLayoutView的onTouch也没触发,而移动手指的时候发现他也可以接收到move以及up事件,我们就明白了,当onIntercept事件来之后,他会判断dispatchTouch是否要拦截,如果拦截了也就不再触发onTouch,相当于要是dispatchTouch都处理了都能分发事件了就说明它已经接收了这个事件所以onTouch就不用触发了。


综上所述,要做一个上拉加载,下拉刷新的功能,首先需要一个布局,并且,布局结构用一个LinearLayout其中包含一个HeadView (头),TailView(尾),ListView(内容)。并且LinearLayout要是自定义的,就以Pull2RefreshView命名。然后想到的组合方法是,onFinishInflate,onMeasure,onLayout,以及dispatchTouchEvent。

onFinishInflate作用:当我们加载布局的时候,触发到这个方法说明已经加载完成,我们可以在这里通过findBiewByID找到head,tail,content-View.

onMeasure 作用:我们可以在这里拿到它的大小,以便在定义位置的时候通过view的大小来计算一个view layout方法里面的left,top,right,bottom.

onLayout作用:重要,主要就是为发根据偏移量动态的去改变view的位置

dispatchTouchEvent作用:用来监听手指移动的偏移量,因为包含了一个ListView,ListView会处理onTouch事件,那么LinearLayout就无法监听到onTouch事件了,而不管怎么样,dispatchTouchEvent总是可以监听到的,所以打算用dispatchTouchEvent来做监听事件的方法。



前面实现了view随移动而下拉,基本的移动实现了,下面完善,下拉显示头部,上拉显示尾部,松开手时,回到原位。

昨天测试了很久,发现如果contentview不定义高度,尾部不管怎么样都不会显示出来。后来发现问题就是出现在Pull2RefreshView继承的是LinearLayout导致的,为什么呢,因为LinearLayout放的子元素是线性的,当元素高度超过了屏幕宽度,那么超出的子view就不会被绘制,也不会被触发measure从而得不到他的大小,如果你强制取得taiview的大小,通过view.onMeasure(0,0)方法,这里会触发View去计算大小,但是tailview还是有子view的,所以tailview的子view大小得不到,你只能看到taiview的根view而看不到包装在里面的子View了。为了解决这个问题,我们打算把Pull2RefreshView改成继承于RelativeLayout,就可以了,因为RelativeLayout是一个View一个view的添加上去的,必定会在屏幕中,除非你设置在屏幕之外那就跟LinearLayout一样了,当然如果坚持用LinearLayout的话也是可以的,你必须给Tailview里边的所有子view都显示的定义他们的大小。这样在绘制的时候就能自动获取到大小了。

1:在监听事件的时候如果只复写dispactchTouch返回true还不够,因为事件会从interceptTouch传给contentView所以暂且把事件都返回true,

2:listivew里面的数据多少是不定的,如果超出屏幕要,那么就得判断当前listview显示的是否是在最顶部,或者在最底部,如果不是这两者就把事件传递给listview让他自己处理,如果是在最顶部就开启下拉显示头部,如果在最底部开启显示上拉尾部。

3:下拉的距离也要判断,只有大于刷新的头部,或者加载的尾部才会停留并刷新,或者加载

现在事儿比较多,,先把demo放上来记一下吧,过两天再完善,现在已经实现最基本的展示。

Demo地址







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值