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的实现原理就完全的搞明白了,以后如果做滚动类的自定义控件,应该也有思路了。