XListview是一个非常受欢迎的下拉刷新控件,但是已经停止维护了。之前写过一篇XListview的使用介绍,用起来非常简单,这两天放假无聊,研究了下XListview的实现原理,学到了很多,今天分享给大家。

    提前声明,为了让代码更好的理解,我对代码进行了部分删减和重构,如果大家想看原版代码,请去github自行下载。

    Xlistview项目主要是三部分:XlistView,XListViewHeader,XListViewFooter,分别是XListView主体、header、footer的实现。下面我们分开来介绍。

    下面是修改之后的XListViewHeader代码


[java] 

1. public class XListViewHeader extends LinearLayout {  
2.   
3. private static final String HINT_NORMAL = "下拉刷新";  
4. private static final String HINT_READY = "松开刷新数据";  
5. private static final String HINT_LOADING = "正在加载...";  
6.   
7. // 正常状态  
8. public final static int STATE_NORMAL = 0;  
9. // 准备刷新状态,也就是箭头方向发生改变之后的状态  
10. public final static int STATE_READY = 1;  
11. // 刷新状态,箭头变成了progressBar  
12. public final static int STATE_REFRESHING = 2;  
13. // 布局容器,也就是根布局  
14. private LinearLayout container;  
15. // 箭头图片  
16. private ImageView mArrowImageView;  
17. // 刷新状态显示  
18. private ProgressBar mProgressBar;  
19. // 说明文本  
20. private TextView mHintTextView;  
21. // 记录当前的状态  
22. private int mState;  
23. // 用于改变箭头的方向的动画  
24. private Animation mRotateUpAnim;  
25. private Animation mRotateDownAnim;  
26. // 动画持续时间  
27. private final int ROTATE_ANIM_DURATION = 180;  
28.   
29. public XListViewHeader(Context context) {  
30. super(context);  
31.         initView(context);  
32.     }  
33.   
34. public XListViewHeader(Context context, AttributeSet attrs) {  
35. super(context, attrs);  
36.         initView(context);  
37.     }  
38.   
39. private void initView(Context context) {  
40.         mState = STATE_NORMAL;  
41. // 初始情况下,设置下拉刷新view高度为0  
42. new LinearLayout.LayoutParams(  
43. 0);  
44.         container = (LinearLayout) LayoutInflater.from(context).inflate(  
45. null);  
46.         addView(container, lp);  
47. // 初始化控件  
48.         mArrowImageView = (ImageView) findViewById(R.id.xlistview_header_arrow);  
49.         mHintTextView = (TextView) findViewById(R.id.xlistview_header_hint_textview);  
50.         mProgressBar = (ProgressBar) findViewById(R.id.xlistview_header_progressbar);  
51. // 初始化动画  
52. new RotateAnimation(0.0f, -180.0f,  
53. 0.5f, Animation.RELATIVE_TO_SELF,  
54. 0.5f);  
55.         mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);  
56. true);  
57. new RotateAnimation(-180.0f, 0.0f,  
58. 0.5f, Animation.RELATIVE_TO_SELF,  
59. 0.5f);  
60.         mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);  
61. true);  
62.     }  
63.   
64. // 设置header的状态  
65. public void setState(int state) {  
66. if (state == mState)  
67. return;  
68.   
69. // 显示进度  
70. if (state == STATE_REFRESHING) {  
71.             mArrowImageView.clearAnimation();  
72.             mArrowImageView.setVisibility(View.INVISIBLE);  
73.             mProgressBar.setVisibility(View.VISIBLE);  
74. else {  
75. // 显示箭头  
76.             mArrowImageView.setVisibility(View.VISIBLE);  
77.             mProgressBar.setVisibility(View.INVISIBLE);  
78.         }  
79.   
80. switch (state) {  
81. case STATE_NORMAL:  
82. if (mState == STATE_READY) {  
83.                 mArrowImageView.startAnimation(mRotateDownAnim);  
84.             }  
85. if (mState == STATE_REFRESHING) {  
86.                 mArrowImageView.clearAnimation();  
87.             }  
88.             mHintTextView.setText(HINT_NORMAL);  
89. break;  
90. case STATE_READY:  
91. if (mState != STATE_READY) {  
92.                 mArrowImageView.clearAnimation();  
93.                 mArrowImageView.startAnimation(mRotateUpAnim);  
94.                 mHintTextView.setText(HINT_READY);  
95.             }  
96. break;  
97. case STATE_REFRESHING:  
98.             mHintTextView.setText(HINT_LOADING);  
99. break;  
100.         }  
101.   
102.         mState = state;  
103.     }  
104.   
105. public void setVisiableHeight(int height) {  
106. if (height < 0)  
107. 0;  
108.         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) container  
109.                 .getLayoutParams();  
110.         lp.height = height;  
111.         container.setLayoutParams(lp);  
112.     }  
113.   
114. public int getVisiableHeight() {  
115. return container.getHeight();  
116.     }  
117.   
118. public void show() {  
119.         container.setVisibility(View.VISIBLE);  
120.     }  
121.   
122. public void hide() {  
123.         container.setVisibility(View.INVISIBLE);  
124.     }  
125.   
126. }



    XListViewHeader继承自linearLayout,用来实现下拉刷新时的界面展示,可以分为三种状态:正常、准备刷新、正在加载。


    在Linearlayout布局里面,主要有指示箭头、说明文本、圆形加载条三个控件。在构造函数中,调用了initView()进行控件的初始化操作。在添加布局文件的时候,指定高度为0,这是为了隐藏header,然后初始化动画,是为了完成箭头的旋转动作。

    setState()是设置header的状态,因为header需要根据不同的状态,完成控件隐藏、显示、改变文字等操作,这个方法主要是在XListView里面调用。除此之外,还有setVisiableHeight()和getVisiableHeight(),这两个方法是为了设置和获取Header中根布局文件的高度属性,从而完成拉伸和收缩的效果,而show()和hide()则显然就是完成显示和隐藏的效果。

    下面是Header的布局文件


[html] 

1. <?xml version="1.0" encoding="utf-8"?>  
2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
3. xmlns:tools="http://schemas.android.com/tools"  
4. android:layout_width="match_parent"  
5. android:layout_height="wrap_content"  
6. android:gravity="bottom" >  
7.   
8. <RelativeLayout  
9. android:id="@+id/xlistview_header_content"  
10. android:layout_width="match_parent"  
11. android:layout_height="60dp"  
12. tools:ignore="UselessParent" >  
13.   
14. <TextView  
15. android:id="@+id/xlistview_header_hint_textview"  
16. android:layout_width="100dp"  
17. android:layout_height="wrap_content"  
18. android:layout_centerInParent="true"  
19. android:gravity="center"  
20. android:text="正在加载"  
21. android:textColor="@android:color/black"  
22. android:textSize="14sp" />  
23.   
24. <ImageView  
25. android:id="@+id/xlistview_header_arrow"  
26. android:layout_width="30dp"  
27. android:layout_height="wrap_content"  
28. android:layout_centerVertical="true"  
29. android:layout_toLeftOf="@id/xlistview_header_hint_textview"  
30. android:src="@drawable/xlistview_arrow" />  
31.   
32. <ProgressBar  
33. android:id="@+id/xlistview_header_progressbar"  
34. style="@style/progressbar_style"  
35. android:layout_width="30dp"  
36. android:layout_height="30dp"  
37. android:layout_centerVertical="true"  
38. android:layout_toLeftOf="@id/xlistview_header_hint_textview"  
39. android:visibility="invisible" />  
40. </RelativeLayout>  
41.   
42. </LinearLayout>



    说完了Header,我们再看看Footer。Footer是为了完成加载更多功能时候的界面展示,基本思路和Header是一样的,下面是Footer的代码



[java] 

    1. public class XListViewFooter extends LinearLayout {  
    2.   
    3. // 正常状态  
    4. public final static int STATE_NORMAL = 0;  
    5. // 准备状态  
    6. public final static int STATE_READY = 1;  
    7. // 加载状态  
    8. public final static int STATE_LOADING = 2;  
    9.   
    10. private View mContentView;  
    11. private View mProgressBar;  
    12. private TextView mHintView;  
    13.   
    14. public XListViewFooter(Context context) {  
    15. super(context);  
    16.         initView(context);  
    17.     }  
    18.   
    19. public XListViewFooter(Context context, AttributeSet attrs) {  
    20. super(context, attrs);  
    21.         initView(context);  
    22.     }  
    23.   
    24. private void initView(Context context) {  
    25.   
    26.         LinearLayout moreView = (LinearLayout) LayoutInflater.from(context)  
    27. null);  
    28.         addView(moreView);  
    29. new LinearLayout.LayoutParams(  
    30.                 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));  
    31.   
    32.         mContentView = moreView.findViewById(R.id.xlistview_footer_content);  
    33.         mProgressBar = moreView.findViewById(R.id.xlistview_footer_progressbar);  
    34.         mHintView = (TextView) moreView  
    35.                 .findViewById(R.id.xlistview_footer_hint_textview);  
    36.     }  
    37.   
    38. /**
    39.      * 设置当前的状态
    40.      * 
    41.      * @param state
    42.      */  
    43. public void setState(int state) {  
    44.   
    45.         mProgressBar.setVisibility(View.INVISIBLE);  
    46.         mHintView.setVisibility(View.INVISIBLE);  
    47.   
    48. switch (state) {  
    49. case STATE_READY:  
    50.             mHintView.setVisibility(View.VISIBLE);  
    51.             mHintView.setText(R.string.xlistview_footer_hint_ready);  
    52. break;  
    53.   
    54. case STATE_NORMAL:  
    55.             mHintView.setVisibility(View.VISIBLE);  
    56.             mHintView.setText(R.string.xlistview_footer_hint_normal);  
    57. break;  
    58.   
    59. case STATE_LOADING:  
    60.             mProgressBar.setVisibility(View.VISIBLE);  
    61. break;  
    62.   
    63.         }  
    64.   
    65.     }  
    66.   
    67. public void setBottomMargin(int height) {  
    68. if (height > 0) {  
    69.   
    70.             LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView  
    71.                     .getLayoutParams();  
    72.             lp.bottomMargin = height;  
    73.             mContentView.setLayoutParams(lp);  
    74.         }  
    75.     }  
    76.   
    77. public int getBottomMargin() {  
    78.         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView  
    79.                 .getLayoutParams();  
    80. return lp.bottomMargin;  
    81.     }  
    82.   
    83. public void hide() {  
    84.         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView  
    85.                 .getLayoutParams();  
    86. 0;  
    87.         mContentView.setLayoutParams(lp);  
    88.     }  
    89.   
    90. public void show() {  
    91.         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView  
    92.                 .getLayoutParams();  
    93.         lp.height = LayoutParams.WRAP_CONTENT;  
    94.         mContentView.setLayoutParams(lp);  
    95.     }  
    96.   
    97. }



        从上面的代码里面,我们可以看出,footer和header的思路是一样的,只不过,footer的拉伸和显示效果不是通过高度来模拟的,而是通过设置BottomMargin来完成的。


        下面是Footer的布局文件


    [html] 

      1. <?xml version="1.0" encoding="utf-8"?>  
      2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
      3. xmlns:tools="http://schemas.android.com/tools"  
      4. android:layout_width="fill_parent"  
      5. android:layout_height="wrap_content" >  
      6.   
      7. <RelativeLayout  
      8. android:id="@+id/xlistview_footer_content"  
      9. android:layout_width="fill_parent"  
      10. android:layout_height="wrap_content"  
      11. android:padding="5dp"  
      12. tools:ignore="UselessParent" >  
      13.   
      14. <ProgressBar  
      15. android:id="@+id/xlistview_footer_progressbar"  
      16. style="@style/progressbar_style"  
      17. android:layout_width="30dp"  
      18. android:layout_height="30dp"  
      19. android:layout_centerInParent="true"  
      20. android:visibility="invisible" />  
      21.   
      22. <TextView  
      23. android:id="@+id/xlistview_footer_hint_textview"  
      24. android:layout_width="wrap_content"  
      25. android:layout_height="wrap_content"  
      26. android:layout_centerInParent="true"  
      27. android:text="@string/xlistview_footer_hint_normal"  
      28. android:textColor="@android:color/black"  
      29. android:textSize="14sp" />  
      30. </RelativeLayout>  
      31.   
      32. </LinearLayout>

          在了解了Header和footer之后,我们就要介绍最核心的XListView的代码实现了。


          在介绍代码实现之前,我先介绍一下XListView的实现原理。

          首先,一旦使用XListView,Footer和Header就已经添加到我们的ListView上面了,XListView就是通过继承ListView,然后处理了屏幕点击事件和控制滑动实现效果的。所以,如果我们的Adapter中getCount()返回的值是20,那么其实XListView里面是有20+2个item的,这个数量即使我们关闭了XListView的刷新和加载功能,也是不会变化的。Header和Footer通过addHeaderView和addFooterView添加上去之后,如果想实现下拉刷新和上拉加载功能,那么就必须有拉伸效果,所以就像上面的那样,Header是通过设置height,Footer是通过设置BottomMargin来模拟拉伸效果。那么回弹效果呢?仅仅通过设置高度或者是间隔是达不到模拟回弹效果的,因此,就需要用Scroller来实现模拟回弹效果。在说明原理之后,我们开始介绍XListView的核心实现原理。

          再次提示,下面的代码经过我重构了,只是为了看起来更好的理解。


      [java]

        1. public class XListView extends ListView {  
        2.   
        3. private final static int SCROLLBACK_HEADER = 0;  
        4. private final static int SCROLLBACK_FOOTER = 1;  
        5. // 滑动时长  
        6. private final static int SCROLL_DURATION = 400;  
        7. // 加载更多的距离  
        8. private final static int PULL_LOAD_MORE_DELTA = 100;  
        9. // 滑动比例  
        10. private final static float OFFSET_RADIO = 2f;  
        11. // 记录按下点的y坐标  
        12. private float lastY;  
        13. // 用来回滚  
        14. private Scroller scroller;  
        15. private IXListViewListener mListViewListener;  
        16. private XListViewHeader headerView;  
        17. private RelativeLayout headerViewContent;  
        18. // header的高度  
        19. private int headerHeight;  
        20. // 是否能够刷新  
        21. private boolean enableRefresh = true;  
        22. // 是否正在刷新  
        23. private boolean isRefreashing = false;  
        24. // footer  
        25. private XListViewFooter footerView;  
        26. // 是否可以加载更多  
        27. private boolean enableLoadMore;  
        28. // 是否正在加载  
        29. private boolean isLoadingMore;  
        30. // 是否footer准备状态  
        31. private boolean isFooterAdd = false;  
        32. // total list items, used to detect is at the bottom of listview.  
        33. private int totalItemCount;  
        34. // 记录是从header还是footer返回  
        35. private int mScrollBack;  
        36.   
        37. private static final String TAG = "XListView";  
        38.   
        39. public XListView(Context context) {  
        40. super(context);  
        41.         initView(context);  
        42.     }  
        43.   
        44. public XListView(Context context, AttributeSet attrs) {  
        45. super(context, attrs);  
        46.         initView(context);  
        47.     }  
        48.   
        49. public XListView(Context context, AttributeSet attrs, int defStyle) {  
        50. super(context, attrs, defStyle);  
        51.         initView(context);  
        52.     }  
        53.   
        54. private void initView(Context context) {  
        55.   
        56. new Scroller(context, new DecelerateInterpolator());  
        57.   
        58. new XListViewHeader(context);  
        59. new XListViewFooter(context);  
        60.   
        61.         headerViewContent = (RelativeLayout) headerView  
        62.                 .findViewById(R.id.xlistview_header_content);  
        63.         headerView.getViewTreeObserver().addOnGlobalLayoutListener(  
        64. new OnGlobalLayoutListener() {  
        65. @SuppressWarnings("deprecation")  
        66. @Override  
        67. public void onGlobalLayout() {  
        68.                         headerHeight = headerViewContent.getHeight();  
        69.                         getViewTreeObserver()  
        70. this);  
        71.                     }  
        72.                 });  
        73.         addHeaderView(headerView);  
        74.   
        75.     }  
        76.   
        77. @Override  
        78. public void setAdapter(ListAdapter adapter) {  
        79. // 确保footer最后添加并且只添加一次  
        80. if (isFooterAdd == false) {  
        81. true;  
        82.             addFooterView(footerView);  
        83.         }  
        84. super.setAdapter(adapter);  
        85.   
        86.     }  
        87.   
        88. @Override  
        89. public boolean onTouchEvent(MotionEvent ev) {  
        90.   
        91.         totalItemCount = getAdapter().getCount();  
        92. switch (ev.getAction()) {  
        93. case MotionEvent.ACTION_DOWN:  
        94. // 记录按下的坐标  
        95.             lastY = ev.getRawY();  
        96. break;  
        97. case MotionEvent.ACTION_MOVE:  
        98. // 计算移动距离  
        99. float deltaY = ev.getRawY() - lastY;  
        100.             lastY = ev.getRawY();  
        101. // 是第一项并且标题已经显示或者是在下拉  
        102. if (getFirstVisiblePosition() == 0  
        103. 0 || deltaY > 0)) {  
        104.                 updateHeaderHeight(deltaY / OFFSET_RADIO);  
        105. else if (getLastVisiblePosition() == totalItemCount - 1  
        106. 0 || deltaY < 0)) {  
        107.                 updateFooterHeight(-deltaY / OFFSET_RADIO);  
        108.             }  
        109. break;  
        110.   
        111. case MotionEvent.ACTION_UP:  
        112.   
        113. if (getFirstVisiblePosition() == 0) {  
        114. if (enableRefresh  
        115.                         && headerView.getVisiableHeight() > headerHeight) {  
        116. true;  
        117.                     headerView.setState(XListViewHeader.STATE_REFRESHING);  
        118. if (mListViewListener != null) {  
        119.                         mListViewListener.onRefresh();  
        120.                     }  
        121.                 }  
        122.                 resetHeaderHeight();  
        123. else if (getLastVisiblePosition() == totalItemCount - 1) {  
        124. if (enableLoadMore  
        125.                         && footerView.getBottomMargin() > PULL_LOAD_MORE_DELTA) {  
        126.                     startLoadMore();  
        127.                 }  
        128.                 resetFooterHeight();  
        129.             }  
        130. break;  
        131.         }  
        132. return super.onTouchEvent(ev);  
        133.     }  
        134.   
        135. @Override  
        136. public void computeScroll() {  
        137.   
        138. // 松手之后调用  
        139. if (scroller.computeScrollOffset()) {  
        140.   
        141. if (mScrollBack == SCROLLBACK_HEADER) {  
        142.                 headerView.setVisiableHeight(scroller.getCurrY());  
        143. else {  
        144.                 footerView.setBottomMargin(scroller.getCurrY());  
        145.             }  
        146.             postInvalidate();  
        147.         }  
        148. super.computeScroll();  
        149.   
        150.     }  
        151.   
        152. public void setPullRefreshEnable(boolean enable) {  
        153.         enableRefresh = enable;  
        154.   
        155. if (!enableRefresh) {  
        156.             headerView.hide();  
        157. else {  
        158.             headerView.show();  
        159.         }  
        160.     }  
        161.   
        162. public void setPullLoadEnable(boolean enable) {  
        163.         enableLoadMore = enable;  
        164. if (!enableLoadMore) {  
        165.             footerView.hide();  
        166. null);  
        167. else {  
        168. false;  
        169.             footerView.show();  
        170.             footerView.setState(XListViewFooter.STATE_NORMAL);  
        171. new OnClickListener() {  
        172. @Override  
        173. public void onClick(View v) {  
        174.                     startLoadMore();  
        175.                 }  
        176.             });  
        177.         }  
        178.     }  
        179.   
        180. public void stopRefresh() {  
        181. if (isRefreashing == true) {  
        182. false;  
        183.             resetHeaderHeight();  
        184.         }  
        185.     }  
        186.   
        187. public void stopLoadMore() {  
        188. if (isLoadingMore == true) {  
        189. false;  
        190.             footerView.setState(XListViewFooter.STATE_NORMAL);  
        191.         }  
        192.     }  
        193.   
        194. private void updateHeaderHeight(float delta) {  
        195. int) delta  
        196.                 + headerView.getVisiableHeight());  
        197. // 未处于刷新状态,更新箭头  
        198. if (enableRefresh && !isRefreashing) {  
        199. if (headerView.getVisiableHeight() > headerHeight) {  
        200.                 headerView.setState(XListViewHeader.STATE_READY);  
        201. else {  
        202.                 headerView.setState(XListViewHeader.STATE_NORMAL);  
        203.             }  
        204.         }  
        205.   
        206.     }  
        207.   
        208. private void resetHeaderHeight() {  
        209. // 当前的可见高度  
        210. int height = headerView.getVisiableHeight();  
        211. // 如果正在刷新并且高度没有完全展示  
        212. if ((isRefreashing && height <= headerHeight) || (height == 0)) {  
        213. return;  
        214.         }  
        215. // 默认会回滚到header的位置  
        216. int finalHeight = 0;  
        217. // 如果是正在刷新状态,则回滚到header的高度  
        218. if (isRefreashing && height > headerHeight) {  
        219.             finalHeight = headerHeight;  
        220.         }  
        221.         mScrollBack = SCROLLBACK_HEADER;  
        222. // 回滚到指定位置  
        223. 0, height, 0, finalHeight - height,  
        224.                 SCROLL_DURATION);  
        225. // 触发computeScroll  
        226.         invalidate();  
        227.     }  
        228.   
        229. private void updateFooterHeight(float delta) {  
        230. int height = footerView.getBottomMargin() + (int) delta;  
        231. if (enableLoadMore && !isLoadingMore) {  
        232. if (height > PULL_LOAD_MORE_DELTA) {  
        233.                 footerView.setState(XListViewFooter.STATE_READY);  
        234. else {  
        235.                 footerView.setState(XListViewFooter.STATE_NORMAL);  
        236.             }  
        237.         }  
        238.         footerView.setBottomMargin(height);  
        239.   
        240.     }  
        241.   
        242. private void resetFooterHeight() {  
        243. int bottomMargin = footerView.getBottomMargin();  
        244. if (bottomMargin > 0) {  
        245.             mScrollBack = SCROLLBACK_FOOTER;  
        246. 0, bottomMargin, 0, -bottomMargin,  
        247.                     SCROLL_DURATION);  
        248.             invalidate();  
        249.         }  
        250.     }  
        251.   
        252. private void startLoadMore() {  
        253. true;  
        254.         footerView.setState(XListViewFooter.STATE_LOADING);  
        255. if (mListViewListener != null) {  
        256.             mListViewListener.onLoadMore();  
        257.         }  
        258.     }  
        259.   
        260. public void setXListViewListener(IXListViewListener l) {  
        261.         mListViewListener = l;  
        262.     }  
        263.   
        264. public interface IXListViewListener {  
        265.   
        266. public void onRefresh();  
        267.   
        268. public void onLoadMore();  
        269.     }  
        270. }



            在三个构造函数中,都调用initView进行了header和footer的初始化,并且定义了一个Scroller,并传入了一个减速的插值器,为了模仿回弹效果。在initView方法里面,因为header可能还没初始化完毕,所以通过GlobalLayoutlistener来获取了header的高度,然后addHeaderView添加到了listview上面。

            通过重写setAdapter方法,保证Footer最后天假,并且只添加一次。

            最重要的,要属onTouchEvent了。在方法开始之前,通过getAdapter().getCount()获取到了item的总数,便于计算位置。这个操作在源代码中是通过scrollerListener完成的,因为ScrollerListener在这里没大有用,所以我直接去掉了,然后把位置改到了这里。如果在setAdapter里面获取的话,只能获取到没有header和footer的item数量。

            在ACTION_DOWN里面,进行了lastY的初始化,lastY是为了判断移动方向的,因为在ACTION_MOVE里面,通过ev.getRawY()-lastY可以计算出手指的移动趋势,如果>0,那么就是向下滑动,反之向上。getRowY()是获取元Y坐标,意思就是和Window和View坐标没有关系的坐标,代表在屏幕上的绝对位置。然后在下面的代码里面,如果第一项可见并且header的可见高度>0或者是向下滑动,就说明用户在向下拉动或者是向上拉动header,也就是指示箭头显示的时候的状态,这时候调用了updateHeaderHeight,来更新header的高度,实现header可以跟随手指动作上下移动。这里有个OFFSET_RADIO,这个值是一个移动比例,就是说,你手指在Y方向上移动400px,如果比例是2,那么屏幕上的控件移动就是400px/2=200px,可以通过这个值来控制用户的滑动体验。下面的关于footer的判断与此类似,不再赘述。

           当用户移开手指之后,ACTION_UP方法就会被调用。在这里面,只对可见位置是0和item总数-1的位置进行了处理,其实正好对应header和footer。如果位置是0,并且可以刷新,然后当前的header可见高度>原始高度的话,就说明用户确实是要进行刷新操作,所以通过setState改变header的状态,如果有监听器的话,就调用onRefresh方法,然后调用resetHeaderHeight初始化header的状态,因为footer的操作如出一辙,所以不再赘述。但是在footer中有一个PULL_LOAD_MORE_DELTA,这个值是加载更多触发条件的临界值,只有footer的间隔超过这个值之后,才能够触发加载更多的功能,因此我们可以修改这个值来改变用户体验。

            说到现在,大家应该明白基本的原理了,其实XListView就是通过对用户手势的方向和距离的判断,来动态的改变Header和Footer实现的功能,所以如果我们也有类似的需求,就可以参照这种思路进行自定义。

            下面再说几个比较重要的方法。

            前面我们说道,在ACTION_MOVE里面,会不断的调用下面的updateXXXX方法,来动态的改变header和fooer的状态,


        [java] 

        1. private void updateHeaderHeight(float delta) {  
        2. int) delta  
        3.                 + headerView.getVisiableHeight());  
        4. // 未处于刷新状态,更新箭头  
        5. if (enableRefresh && !isRefreashing) {  
        6. if (headerView.getVisiableHeight() > headerHeight) {  
        7.                 headerView.setState(XListViewHeader.STATE_READY);  
        8. else {  
        9.                 headerView.setState(XListViewHeader.STATE_NORMAL);  
        10.             }  
        11.         }  
        12.   
        13.     }  
        14.   
        15. private void updateFooterHeight(float delta) {  
        16. int height = footerView.getBottomMargin() + (int) delta;  
        17. if (enableLoadMore && !isLoadingMore) {  
        18. if (height > PULL_LOAD_MORE_DELTA) {  
        19.                 footerView.setState(XListViewFooter.STATE_READY);  
        20. else {  
        21.                 footerView.setState(XListViewFooter.STATE_NORMAL);  
        22.             }  
        23.         }  
        24.         footerView.setBottomMargin(height);  
        25.   
        26.     }


            在移开手指之后,会调用下面的resetXXX来初始化header和footer的状态


        [java] 

        1. private void resetHeaderHeight() {  
        2. // 当前的可见高度  
        3. int height = headerView.getVisiableHeight();  
        4. // 如果正在刷新并且高度没有完全展示  
        5. if ((isRefreashing && height <= headerHeight) || (height == 0)) {  
        6. return;  
        7.         }  
        8. // 默认会回滚到header的位置  
        9. int finalHeight = 0;  
        10. // 如果是正在刷新状态,则回滚到header的高度  
        11. if (isRefreashing && height > headerHeight) {  
        12.             finalHeight = headerHeight;  
        13.         }  
        14.         mScrollBack = SCROLLBACK_HEADER;  
        15. // 回滚到指定位置  
        16. 0, height, 0, finalHeight - height,  
        17.                 SCROLL_DURATION);  
        18. // 触发computeScroll  
        19.         invalidate();  
        20.     }  
        21.   
        22. private void resetFooterHeight() {  
        23. int bottomMargin = footerView.getBottomMargin();  
        24. if (bottomMargin > 0) {  
        25.             mScrollBack = SCROLLBACK_FOOTER;  
        26. 0, bottomMargin, 0, -bottomMargin,  
        27.                     SCROLL_DURATION);  
        28.             invalidate();  
        29.         }  
        30.     }


            我们可以看到,滚动操作不是通过直接的设置高度来实现的,而是通过Scroller.startScroll()来实现的,通过调用此方法,computeScroll()就会被调用,然后在这个里面,根据mScrollBack区分是哪一个滚动,然后再通过设置高度和间隔,就可以完成收缩的效果了。


            至此,整个XListView的实现原理就完全的搞明白了,以后如果做滚动类的自定义控件,应该也有思路了。