1 PullToRefreshListView的作用

   目前,我们在使用很多应用的时候,都学会一个方法,那就是下拉刷新,不论是新闻类的APP界面,下拉刷新界面加载最新数据,上拉刷出列表加载界面;还是交友类APP,下拉刷新最新的数据;亦或是购物类APP下拉刷新最新商品推送。目前有太多的应用使用该种方法提升用户体验,在视频播放其中,我们利用下拉屏幕,就可以刷新最新视频库的资源推送给用户。当然,上面说的这些功能基本都是依靠PullToRefreshListView来完成的。

   那么在程序中我们该如何实现下拉刷新。

  1)首先需要定义变量mPullRefreshListView;

  2)初始化mPullRefreshListView并设置监听器,以执行当需要刷新时代码。如图1所示,对应代码为:

图1 初始化mPullRefreshListView并设置监听器  

 

图2 设置监听刷新刷新调用代码

  设置监听刷新时执行如下代码,调用setLastUpdatedLabelAndCompletRefresh方法,代码如图2所示。

  如图3所示在PullToRefreshListView中有三个View,其中headerView一共有2LoadingView,一个是被加入到LinearLayout,一个是被加入到ListView的HeaderView。

  图3 HeaderView结构

  addViewInternal方法就是加入到LinearLayout父类中看看LoadingLayout 有2种 FlipLoadingLayout 和 RotateLoadingLayout 一般我们用旋转的加载动画,左边一个旋转图片,右边是文字和时间提示第一个LoadingLayout主要显示:松开即可刷新(往下拉),下拉即可刷新(再往上划),第二个LoadingLayout显示松手后的文字:正在加载,加载完成之后显示:已更新。如上图所示结构。

图4 顶端拉动HeaderView显示  

图5 顶端拉动HeaderView后刷新显示

2. PullToRefreshListView创建的流程

  如下图6所示,在OnlineVideoHomepageFragment中,创建了PullToRefreshListView的实例。

图6 OnlineVideoHomepageFragment中使用PullToRefreshListView

  下面将介绍在创建PullToRefreshListView的流程。如图7所示为创建PullToRefreshListView的流程。

图7 创建PullToRefreshListView的流程

1)首先创建PullToRefreshListView,调用了createRefreshableView(Context context, AttributeSet attrs)方法,在该方法中接着调用方法createListView(context, attrs)。

2)在createListView(context, attrs)中,创建InternalListView(context, attrs)的实例。

3)调用handleStyledAttributes(TypedArray a)方法,创建mHeaderLoadingView和mFooterLoadingView,并将setScrollingWhileRefreshingEnabled设置为true,即下滑后View设置为可滑动。

4)调用setFragmentLastUpdatedLabelAndCompletRefresh完成刷新。

5)最后调用onRefreshComplete(),也就是调用PullToRefreshBase中的setState(State.RESET)方法,设置状态。 加载“推介”界面,不进行上拉,在创建View需按上述步骤进行调用。

3. PullToRefreshListView调用其方法的时序  

 

图8 PullToRefreshListView继承关系

  整个下拉刷新父View是LinearLayout,在LinearLayout添加了Header View ,Footer View和ListView。PullToRefreshBase 是父类扩展了 LinearLayout水平布局如果我们使用ListView 需要观看子类PullToRefreshAdapterViewBase -> PullToRefreshListView。如图8所示为PullToRefreshListView继承关系。

  在第一次上拉刷新界面时,调用onRefreshing,通过getCurrentMode()获取上拉为PULL_FROM_START,拉到顶端将执行switch的default中的程序。如果不是第一次拉动,在再次拉动前调用方法onReset()。如图9所示是PullToRefreshListView调用其方法的时序图。

 

图9 PullToRefreshListView调用其方法的时序图

4. PullToRefreshListView详解

(1)InternalListView类

  该类的继承关系如下: InternalListView->VelocityListView->AutoScrollListView->ListView 该类重写了dispatchDraw ()和dispatchTouchEvent()方法,对于dispatchTouchEvent详解如下。 当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View, TouchEvent最先到达最顶层 view的 dispatchTouchEvent ,然后由  dispatchTouchEvent 方法进行分发,如果dispatchTouchEvent返回true ,则交给该View的onTouchEvent处理,如果dispatchTouchEvent返回 false ,则交给该View的 interceptTouchEvent 方法来决定是否要拦截这个事件,如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。如果事件传递到某一层的子 view 的 onTouchEvent 上了,这个方法返回了 false ,那么这个事件会从这个 view 往上传递,都是 onTouchEvent 来接收。而如果传递到最上面的 onTouchEvent 也返回 false 的话,这个事件就会“消失”,而且接收不到下一次事件。

(2)handleStyledAttributes方法

  在该方法中,主要完成了创建mHeaderLayoutingView和mFooterLayoutingView。并在创建时进行隐藏,同时通过FrameLayout的addView方法加入View中。handleStyledAttributes方法定位到子类复写的地方: 

FrameLayout frame = new FrameLayout(getContext());
mHeaderLoadingView = createLoadingLayout(getContext(), Mode.PULL_FROM_START, a);
mHeaderLoadingView.setVisibility(View.GONE);
frame.addView(mHeaderLoadingView, lp);
mRefreshableView.addHeaderView(frame, null, false);//添加LoadingView到ListView Header上

(3)PullToRefreshBase

  这个类是PullToRefreshListView的一个核心的类,该类是一个泛型抽象类,ListView,GridView,ScrollView等都需要继承它。

  PullToRefreshBase本质上是一个LinearLayout。 PullToRefreshBase实际上包含了三个View,一个是头部要下拉刷新时候的mHeaderLayout,一个是要加载更多的mFooterLayout,还有一个就是mRefreshableView,这个是泛型,具体是ListView,还是GridView,获取其他View,需要看泛型T。

   代码中,其实就是下面这三个View:

T mRefreshableView;

private LoadingLayout mHeaderLayout;

private LoadingLayout mFooterLayout;

  1)init是类PullToRefreshBase构造方法的核心代码。分析init()方法。

进入init方法之后,会发现,它首先会判断,是上下拉刷新,还是左右拉刷新,左右拉的实现是PullToRefreshHorizontalScrollView,我们这里仅分析上下拉,它们的原理都是一样的。它会先设置一下布局的方向,是上下布局,还是左右布局,通过getPullToRefreshScrollDirection()来判断。就开始给三个View,创建对象并添加到PullToRefreshBase里面。

   2)createRefreshableView是一个抽象方法,它需要实现它的子类来提供T这个对象,比如ListView实现了PullToRefreshBase,他们就由ListView来提供对象。这里,ListView就会返回一个ListView对象,于是,这个泛型T,就是ListView。createLoadingLayout负责创建mHeaderLayout和mFooterLayout。在init方法的最后面,有一个方法叫updateUIForMode(),这里面是隐藏mHeaderLayout和mFooterLayout进入方法updateUIForMode()。

   3)最核心的是refreshLoadingViewsSize()于是,我们进入refreshLoadingViewsSize,看一下它是怎么隐藏头部和尾部的。它会根据方向,首先给头部和尾部设置宽高。然后调用setPadding(pLeft, pTop, pRight, pBottom)来隐藏头部和尾部。

  4)手放到手机屏幕滑动的时候会有滑动事件,会在onTouchEvent里面去做处理,但是,要知道,PullToRefreshBase是一个LinearLayout,而该布局里面的三个View中,有一个是ListView(以下泛型T,都以ListView为例子),而ListView它也有onTouchEvent事件,那么PullToRefreshBase事件与ListView的事件,就发生了冲突。

  5)PullToRefreshBase的事件分发:

  View的事件分发会从最外层的ViewGroup往最内层的View来分发,会走dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent,如果ViewGroup在onInterceptTouchEvent 返回true,则表示要拦截事件,那么被拦截的时间就由当前的ViewGroup的onTouchEvent进行处理。PullToRefreshBase 是一个LinearLayout,LinearLayout也是一个ViewGroup。PullToRefreshBase是在onInterceptTouchEvent进行事件拦截。在代码中有一个方法,叫isReadyForPull(),在MotionEvent.ACTION_MOVE的时候,如果这个方法返回true,mIsBeingDragged也就返回true,于是,就拦截了事件,不把事件分发到ListView里面去,从而使PullToRefreshBase的onTouch来处理改事件,PullToRefreshBase的onTouch就来对下拉或者上拉滑动事件进行处理。

  isReadyForPull会根据Mode来判断是上拉,还是下拉,如果是下拉。就调用isReadyForPullStart()来判断,如果是上拉就调用isReadyForPullEnd(),如果Mode是BOTH就两者都调用。

  如果当前显示的是ListView的最后一个Item,那么,就拦截ListView的上拉事件,让上拉事件给PullToRefreshBase来处理。而ListView只做下拉的滚动。 此时,我们知道了ListView也就是PullToRefreshListView和PullToRefreshBase对事件拦截和分发的处理过程。

(4)onRefreshing

  拉倒最顶部松手,会执行onRefreshing方法,回调实现的任务接口也就是callRefreshListener。如图10所示为onRefreshing方法中switch对应的代码。  

图10 onRefreshing方法switch对应的代码

  对于onRefreshing中获取触屏状态是通过getCurrentMode()获取的,当除pull_from_end外都将执行刷新程序。顶部上拉对应pull_from_start,接着也将执行default。  

 

图11 Mode对应的参数代码

如图11所示为Mode对应的参数代码,其意义见下:

  PULL_FROM_START:仅仅支持下动刷新;

  PULL_FROM_END:仅支持上拉加载更多;

  BOTH:上拉下拉都支持;

  MANUAL_REFRESH_ONLY:只允许手动触发。