PulltoRefresh源码分析

图2

1.前提

      此篇文章分析的源码已被我使用Android Studio重新编译,后传到了我的github中,地址:https://github.com/allen218/PullToRefresh 大家可以到此地址去进行下载。
      今天我们对整个项目里的东西几种实现分别进行分析,在分析之前先大概说下此项目的设计原理吧

2.设计思想

      可以说此项目是在自定义View的基础上完成了功能的实现,设计非常之巧妙.RefreshLayoutBase作为基类继承自ViewGroup,在RefreshLayoutBase完成了下拉刷新部分的布局和加载更多部分的布局,而中间内容部分的布局则下放到子类去完成。同时在RefreshLayoutBase里完成的此控件的onMeasureonLayout的操作,让控件以上、中、下的LinearLayout方式完成的布局。
      还有一个重要的问题就是什么时候下拉刷新以及什么时候加载更多?这里使用是isTop()isBottom两个标志位进行抽象,下放到子类去实现,因为只有子类也就是内容部分知道自己的状态。
      同时使用了状态机来定义了一系列的状态,以标识当前是否可以刷新以及其处于哪个状态,以通过相应的状态来进行操作。
      还有一个重要的地方就是这里的下拉刷新以及加载更多的事件滚动都是通过Scroller来进行的,非常的顺滑。
      说完了大概的设计之后,我们就来一个一个的分析其实现吧。

3.基类RefreshLayoutBase分析

public abstract class RefreshLayoutBase<T extends View> extends ViewGroup implements
    OnScrollListener {

首先继承的是ViewGroup,也就是说这是一个自定义控件,当需要完成onMeasureonLayout的操作

/**
 * 用于滚动
 */
protected Scroller mScroller;

/**
 * 下拉刷新时显示的header view
 */
protected View mHeaderView;

/**
 * 上拉加载更多时显示的footer view
 */
protected View mFooterView;

/**
 * 本次触摸滑动y坐标上的偏移量
 */
protected int mYOffset;

/**
 * 内容视图, 即用户触摸导致下拉刷新、上拉加载的主视图. 比如ListView, GridView等.
 */
protected T mContentView;

/**
 * 最初的滚动位置.第一次布局时滚动header的高度的距离
 */
protected int mInitScrollY = 0;
/**
 * 最后一次触摸事件的y轴坐标
 */
protected int mLastY = 0;

/**
 * 空闲状态
 */
public static final int STATUS_IDLE = 0;

/**
 * 下拉或者上拉状态, 还没有到达可刷新的状态
 */
public static final int STATUS_PULL_TO_REFRESH = 1;

/**
 * 下拉或者上拉状态
 */
public static final int STATUS_RELEASE_TO_REFRESH = 2;
/**
 * 刷新中
 */
public static final int STATUS_REFRESHING = 3;

/**
 * LOADING中
 */
public static final int STATUS_LOADING = 4;

/**
 * 当前状态
 */
protected int mCurrentStatus = STATUS_IDLE;

/**
 * header中的箭头图标
 */
private ImageView mArrowImageView;
/**
 * 箭头是否向上
 */
private boolean isArrowUp;
/**
 * header 中的文本标签
 */
private TextView mTipsTextView;
/**
 * header中的时间标签
 */
private TextView mTimeTextView;
/**
 * header中的进度条
 */
private ProgressBar mProgressBar;
/**
 * 屏幕高度
 */
private int mScreenHeight;
/**
 * Header 高度
 */
private int mHeaderHeight;
/**
 * 下拉刷新监听器
 */
protected OnRefreshListener mOnRefreshListener;
/**
 * 加载更多回调
 */
protected OnLoadListener mLoadListener;

这里定义了一系列的属性,这里主要有下拉刷新显示的Hearder View,以及使用泛型修辞的Content View和加载更多的Footer View.

接下来看看其构造
图1
首先这里使用了一个循环调用来串起了三个构造函数。在构造函数里进行了Scroller的创建,以及对屏幕高度的获取,然后调用initLayout来初始化布局。

图2
在初始化布局里我们完成了HearViewContent ViewFooter View三者的初始化动作,最后将其添加到此ViewGroup控件里
在这里我们看看Content View的初始化处理吧
图3
这里直接将其抽象化,由子类继承去完成其View的布局。这里也顺便列出其它两个需要子类去完成的方法。
图3
这两个方法就是让子类对自已是否处于顶部或者底部做一个处理。

接下来我们就跟着控件的执行顺序来看看其OnMeasure的处理吧。
图3这里主要对子View进行了测量,然后将子View的高度进行了累加,因为这里是自上而下的LinearLayout布局,所以测量起来也相对方便,得到所有子View的高度后,就相当于得到了控件自己的高度,而宽度直接从父View传过来的widthMeasure里通过getSize获取就能得到。最后能过setMeasureDimension设置控件自身的宽高。

图3这里首先为各子View来进行布局,也就是指定各子View显示的位置。然后计算header view的高度,最后通过Scroller滚动其hearder view的高度将其隐藏

经过上面onMeasureonLayout的操作,其控件就可以显示出来了,当然这里还需要子类对content View进行实现。

接下来,就是相关事件分发的处理了。
图3这里首先通过down获得当前触摸点在屏幕上Y轴的距离,然后通过Move来得到差值以判断是否是下拉过程。然后在move环节通过差值和isTop判断是否可以下拉刷新以对当前触摸事件进行拦截处理。

如果onInterceptTouchEvent返回的值为true,也就是说该控件对事件进行了拦截,此时就是调用我们的onTouchEvent方法。(当然这里插一个题外话,如果我们的onTouchListener返回的值是false的话,这里的onTouchEvent是不会被调用的.因为OnTouchListener的事件优化级比onTouchEvent高).
图3这里首先获取到差值,在不是刷新的状态下继续对headerView进行滑动,以及更改一些headerView里的布局信息。最后在up状态下进行刷新的操作。我们再来看看它是怎么进行刷新的吧.

图3这里主要是通过回调来通知监听者更新数据。changeHeaderViewStaus这个方法比较重要。

图3这个方法主要做的事情就是判断是否需要显示下拉刷新。首先得到控件滑动的距离,也就是得到headerView滑动的距离,当距离大于高度的1/2或者小于1/2时,都通过scroller来准备相应的滚动。最后通过invalidate来让scroller工作进来,进行顺滑的滚动。

刷新完成后,需要调用refreshCompeled来对headerView进行复原的处理。图3
到此下拉刷新的操作就已经完成了。接下来要看的是加载更多的是怎么操作的。
加载更多相对来讲就要简单很多了,主要是通过onscroll方法的监听来完成的。我们先来看看onScroll方法的处理吧。这里只处理了ListView的加载更多处理。这里实现了ListViewOnScrollListener图3通过这两个方法就完成了加载更多的操作。
至此RefreshLayoutBase基类就分析完毕了。

4.RefreshListView文件分析

其实基类分析完后,其它的都比较简单了,只需要设置其Content View,然后完成是否可以下拉刷新及加载更多的操作就可以了.图3

5.RefreshSlideDeleteListView文件分析

其它的都和RefreshListView实现上差不多,只有这个RefreshSlideDeleteListView的实现比较特别,所以特意拿出来分析一下。

图3

这里主要是对事件分发的拦截操作,我们先看onInterceptTouchEvent
图3这里的slideItemView方法比较重要,它主要处理的是删除按钮的显示与隐藏的操作,在onTouchEvent方法里也会调用此方法,到时候再进行分析。这里主要看的是一个getItemPosition方法,在这个方法里处理的是当前手指所触摸到的item的position.
图3

当在onInterceptTouchEvent返回true后,会调用onTouchEvent方法图3这里isSlideValid主要是处理滑动删除展开或者收起
图3
到这里RefreshSlideDeleteListView的分析也就完成了。