android下拉刷新功能---教你实现简单的ListView下拉刷新

古城微笑少年丶 2022-08-18 11:35 317阅读 0赞
  1. android中数据的更新需要用户很方便就能操作,其中下拉刷新就是很好的一种用户体验方式,这是郭林大神在网上的一个下拉刷新的demo,我做了一点小小的修改,然后加了一些注释,记录在博客中,以后开发中需要的时候,直接拿过来用。

效果图:

Center

页面布局

1、layout文件下有两个xml文件:

activity_main.xml中代码:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:paddingBottom="@dimen/activity_vertical_margin"
  6. android:paddingLeft="@dimen/activity_horizontal_margin"
  7. android:paddingRight="@dimen/activity_horizontal_margin"
  8. android:paddingTop="@dimen/activity_vertical_margin"
  9. tools:context=".MainActivity" >
  10. <!-- 在项目中引入下拉刷新 -->
  11. <com.demo.pullrefreshlistviewdemo.RefreshableView
  12. android:id="@+id/refreshable_view"
  13. android:layout_width="fill_parent"
  14. android:layout_height="fill_parent" >
  15. <ListView
  16. android:id="@+id/list_view"
  17. android:layout_width="fill_parent"
  18. android:layout_height="fill_parent" >
  19. </ListView>
  20. </com.demo.pullrefreshlistviewdemo.RefreshableView>
  21. </RelativeLayout>

pull_refresh.xml中代码:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:id="@+id/pull_to_refresh_head"
  4. android:layout_width="fill_parent"
  5. android:layout_height="60dip" >
  6. <LinearLayout
  7. android:layout_width="200dip"
  8. android:layout_height="60dip"
  9. android:layout_centerInParent="true"
  10. android:orientation="horizontal" >
  11. <RelativeLayout
  12. android:layout_width="0dip"
  13. android:layout_height="60dip"
  14. android:layout_weight="3" >
  15. <ImageView
  16. android:id="@+id/arrow"
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:layout_centerInParent="true"
  20. android:src="@drawable/arrow" />
  21. <ProgressBar
  22. android:id="@+id/progress_bar"
  23. android:layout_width="30dip"
  24. android:layout_height="30dip"
  25. android:layout_centerInParent="true"
  26. android:visibility="gone" />
  27. </RelativeLayout>
  28. <LinearLayout
  29. android:layout_width="0dip"
  30. android:layout_height="60dip"
  31. android:layout_weight="12"
  32. android:orientation="vertical" >
  33. <TextView
  34. android:id="@+id/description"
  35. android:layout_width="fill_parent"
  36. android:layout_height="0dip"
  37. android:layout_weight="1"
  38. android:gravity="center_horizontal|bottom"
  39. android:text="@string/pull_to_refresh" />
  40. <TextView
  41. android:id="@+id/updated_at"
  42. android:layout_width="fill_parent"
  43. android:layout_height="0dip"
  44. android:layout_weight="1"
  45. android:gravity="center_horizontal|top"
  46. android:text="@string/updated_at" />
  47. </LinearLayout>
  48. </LinearLayout>
  49. </RelativeLayout>

java类:

MainActivity.java中代码:

  1. package com.demo.pullrefreshlistviewdemo;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import android.app.Activity;
  5. import android.os.Bundle;
  6. import android.util.Log;
  7. import android.view.Window;
  8. import android.widget.ArrayAdapter;
  9. import android.widget.ListView;
  10. import com.demo.pullrefreshlistviewdemo.RefreshableView.PullToRefreshListener;
  11. /**
  12. * 在项目中引入ListView下拉刷新功能需三步:
  13. * 1.在Activity的布局文件中加入自定义的RefreshableView,并让ListView包含在其中。
  14. * 2.在Activity中调用RefreshableView的setOnRefreshListener方法注册回调接口。
  15. * 3.在onRefresh方法的最后,记得调用RefreshableView的finishRefreshing方法,通知刷新结束。
  16. * 从此以后,在项目的任何地方,一分钟引入下拉刷新功能妥妥的。
  17. */
  18. public class MainActivity extends Activity {
  19. RefreshableView refreshableView;
  20. ListView listView;
  21. ArrayAdapter<String> adapter;
  22. private static final String TAG = "MainActivity";
  23. List list = new ArrayList();
  24. private int count = 1;// 记录更新的次数
  25. public List initDatas() {
  26. for (int i = 0; i < 10; i++) {
  27. list.add("数据" + i);
  28. }
  29. return list;
  30. }
  31. @Override
  32. protected void onCreate(Bundle savedInstanceState) {
  33. super.onCreate(savedInstanceState);
  34. requestWindowFeature(Window.FEATURE_NO_TITLE);
  35. setContentView(R.layout.activity_main);
  36. /**
  37. * 获得自定义刷新功能布局对象实例
  38. */
  39. refreshableView = (RefreshableView) findViewById(R.id.refreshable_view);
  40. /**
  41. * 获得ListView控件对象实例
  42. */
  43. listView = (ListView) findViewById(R.id.list_view);
  44. initDatas();
  45. /**
  46. * 实例化适配器,将子项布局和数据放到适配器中
  47. */
  48. adapter = new ArrayAdapter<String>(this,
  49. android.R.layout.simple_list_item_1, list);
  50. /**
  51. * 将适配器加载到ListView列表控件中
  52. */
  53. listView.setAdapter(adapter);
  54. /**
  55. * 将adapter对象传到refreshableView中,方便更新新数据到适配器中
  56. */
  57. refreshableView.initAdapter(adapter);
  58. /**
  59. * 监听自定义刷新功能布局事件
  60. */
  61. refreshableView.setOnRefreshListener(new PullToRefreshListener() {
  62. @Override
  63. public void onRefresh() {
  64. try {
  65. Log.d("RefreshableView",
  66. "--进入refreshableView.setOnRefreshListener回调方法");
  67. Thread.sleep(3000);
  68. } catch (InterruptedException e) {
  69. e.printStackTrace();
  70. }
  71. Log.d("RefreshableView",
  72. "--MainActivity中执行refreshableView.finishRefreshing()方法");
  73. refreshableView.finishRefreshing();
  74. }
  75. }, 0);
  76. }
  77. }

RefreshableView.java中代码:

  1. /**
  2. * @author honey 项目启动执行顺序: 1、首先执行RefreshableView构造方法
  3. * 2、执行setOnRefreshListener(PullToRefreshListener, int)方法
  4. * 3、执行onLayout(boolean, int, int, int, int)方法
  5. * 4、滑动时,执行onTouch(View, MotionEvent)方法
  6. */
  7. public class RefreshableView extends LinearLayout implements OnTouchListener {
  8. public String TAG = "RefreshableView";
  9. public ArrayAdapter<String> adapter;
  10. public int count = 0;// 记录更新的数据次数
  11. /**
  12. * 下拉状态
  13. */
  14. public static final int STATUS_PULL_TO_REFRESH = 0;
  15. /**
  16. * 释放立即刷新状态
  17. */
  18. public static final int STATUS_RELEASE_TO_REFRESH = 1;
  19. /**
  20. * 正在刷新状态
  21. */
  22. public static final int STATUS_REFRESHING = 2;
  23. /**
  24. * 刷新完成或未刷新状态
  25. */
  26. public static final int STATUS_REFRESH_FINISHED = 3;
  27. /**
  28. * 下拉头部回滚的速度
  29. */
  30. public static final int SCROLL_SPEED = -20;
  31. /**
  32. * 一分钟的毫秒值,用于判断上次的更新时间
  33. */
  34. public static final long ONE_MINUTE = 60 * 1000;
  35. /**
  36. * 一小时的毫秒值,用于判断上次的更新时间
  37. */
  38. public static final long ONE_HOUR = 60 * ONE_MINUTE;
  39. /**
  40. * 一天的毫秒值,用于判断上次的更新时间
  41. */
  42. public static final long ONE_DAY = 24 * ONE_HOUR;
  43. /**
  44. * 一月的毫秒值,用于判断上次的更新时间
  45. */
  46. public static final long ONE_MONTH = 30 * ONE_DAY;
  47. /**
  48. * 一年的毫秒值,用于判断上次的更新时间
  49. */
  50. public static final long ONE_YEAR = 12 * ONE_MONTH;
  51. /**
  52. * 上次更新时间的字符串常量,用于作为SharedPreferences的键值
  53. */
  54. private static final String UPDATED_AT = "updated_at";
  55. /**
  56. * 下拉刷新的回调接口
  57. */
  58. private PullToRefreshListener mListener;
  59. /**
  60. * 用于存储上次更新时间
  61. */
  62. private SharedPreferences preferences;
  63. /**
  64. * 下拉头的View
  65. */
  66. private View header;
  67. /**
  68. * 需要去下拉刷新的ListView
  69. */
  70. private ListView listView;
  71. /**
  72. * 刷新时显示的进度条
  73. */
  74. private ProgressBar progressBar;
  75. /**
  76. * 指示下拉和释放的箭头
  77. */
  78. private ImageView arrow;
  79. /**
  80. * 指示下拉和释放的文字描述
  81. */
  82. private TextView description;
  83. /**
  84. * 上次更新时间的文字描述
  85. */
  86. private TextView updateAt;
  87. /**
  88. * 下拉头的布局参数
  89. */
  90. private MarginLayoutParams headerLayoutParams;
  91. /**
  92. * 上次更新时间的毫秒值
  93. */
  94. private long lastUpdateTime;
  95. /**
  96. * 为了防止不同界面的下拉刷新在上次更新时间上互相有冲突,使用id来做区分
  97. */
  98. private int mId = -1;
  99. /**
  100. * 下拉头的高度
  101. */
  102. private int hideHeaderHeight;
  103. /**
  104. * 当前处理什么状态,可选值有STATUS_PULL_TO_REFRESH, STATUS_RELEASE_TO_REFRESH,
  105. * STATUS_REFRESHING 和 STATUS_REFRESH_FINISHED
  106. */
  107. private int currentStatus = STATUS_REFRESH_FINISHED;;
  108. /**
  109. * 记录上一次的状态是什么,避免进行重复操作
  110. */
  111. private int lastStatus = currentStatus;
  112. /**
  113. * 手指按下时的屏幕纵坐标
  114. */
  115. private float yDown;
  116. /**
  117. * 在被判定为滚动之前用户手指可以移动的最大值。
  118. */
  119. private int touchSlop;
  120. /**
  121. * 是否已加载过一次layout,这里onLayout中的初始化只需加载一次
  122. */
  123. private boolean loadOnce;
  124. /**
  125. * 当前是否可以下拉,只有ListView滚动到头的时候才允许下拉
  126. */
  127. private boolean ableToPull;
  128. /**
  129. * 下拉刷新控件的构造函数,会在运行时动态添加一个下拉头的布局。 项目启动首先进入这个构造方法
  130. *
  131. * @param context
  132. * @param attrs
  133. */
  134. public RefreshableView(Context context, AttributeSet attrs) {
  135. super(context, attrs);
  136. Log.d(TAG, "进入RefreshableView构造方法");
  137. /**
  138. * 获得SharedPreferences对象
  139. */
  140. preferences = PreferenceManager.getDefaultSharedPreferences(context);
  141. header = LayoutInflater.from(context).inflate(R.layout.pull_refresh,
  142. null, true);
  143. progressBar = (ProgressBar) header.findViewById(R.id.progress_bar);
  144. arrow = (ImageView) header.findViewById(R.id.arrow);
  145. description = (TextView) header.findViewById(R.id.description);
  146. updateAt = (TextView) header.findViewById(R.id.updated_at);
  147. /**
  148. * getScaledTouchSlop是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件
  149. */
  150. touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
  151. refreshUpdatedAtValue();
  152. setOrientation(VERTICAL);
  153. /**
  154. * addView 动态给Activity添加View组件
  155. */
  156. addView(header, 0);
  157. Log.d(TAG, "离开RefreshableView构造方法");
  158. }
  159. /**
  160. * 1、onLayout()是ViewGroup类的抽象方法,子类中必须重写,作用:
  161. * 通过调用其children的layout函数来设置子视图相对与父视图中的位置。
  162. * 2、进行一些关键性的初始化操作,比如:将下拉头向上偏移进行隐藏,给ListView注册touch事件。
  163. */
  164. @Override
  165. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  166. super.onLayout(changed, l, t, r, b);
  167. Log.d(TAG, "进入onLayout方法");
  168. if (changed && !loadOnce) {
  169. Log.d(TAG, "进入onLayout方法中的if语句");
  170. hideHeaderHeight = -header.getHeight();
  171. headerLayoutParams = (MarginLayoutParams) header.getLayoutParams();
  172. headerLayoutParams.topMargin = hideHeaderHeight;
  173. /**
  174. * getChildAt(index)是获取某个指定position的view。
  175. * 注意:在ListView中,使用getChildAt(index)的取值,只能是当前可见区域(列表可滚动)的子项!
  176. */
  177. listView = (ListView) getChildAt(1);
  178. /**
  179. * 监听用户触摸屏事件
  180. */
  181. listView.setOnTouchListener(this);
  182. loadOnce = true;
  183. Log.d(TAG, "离开onLayout方法中的if语句");
  184. }
  185. Log.d(TAG, "离开onLayout方法");
  186. }
  187. /**
  188. * 当ListView被触摸时调用,其中处理了各种下拉刷新的具体逻辑。
  189. */
  190. @Override
  191. public boolean onTouch(View v, MotionEvent event) {
  192. Log.d(TAG, "进入onTouch方法");
  193. setIsAbleToPull(event);
  194. if (ableToPull) {
  195. switch (event.getAction()) {
  196. case MotionEvent.ACTION_DOWN:
  197. yDown = event.getRawY();
  198. Log.d(TAG, "进入MotionEvent.ACTION_DOWN,yDown:" + yDown);
  199. break;
  200. case MotionEvent.ACTION_MOVE:
  201. float yMove = event.getRawY();
  202. int distance = (int) (yMove - yDown);
  203. Log.d(TAG, "进入MotionEvent.ACTION_MOVE,yMove:" + yMove);
  204. // 如果手指是下滑状态,并且下拉头是完全隐藏的,就屏蔽下拉事件
  205. if (distance <= 0
  206. && headerLayoutParams.topMargin <= hideHeaderHeight) {
  207. return false;
  208. }
  209. Log.d(TAG, "touchSlop:" + touchSlop);
  210. if (distance < touchSlop) {
  211. return false;
  212. }
  213. if (currentStatus != STATUS_REFRESHING) {
  214. if (headerLayoutParams.topMargin > 0) {
  215. currentStatus = STATUS_RELEASE_TO_REFRESH;
  216. } else {
  217. currentStatus = STATUS_PULL_TO_REFRESH;
  218. }
  219. // 通过偏移下拉头的topMargin值,来实现下拉效果
  220. headerLayoutParams.topMargin = (distance / 2)
  221. + hideHeaderHeight;
  222. header.setLayoutParams(headerLayoutParams);
  223. }
  224. break;
  225. case MotionEvent.ACTION_UP:
  226. default:
  227. Log.d(TAG, "进入MotionEvent.ACTION_UP或者是default中");
  228. if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
  229. // 松手时如果是释放立即刷新状态,就去调用正在刷新的任务
  230. new RefreshingTask().execute();
  231. } else if (currentStatus == STATUS_PULL_TO_REFRESH) {
  232. // 松手时如果是下拉状态,就去调用隐藏下拉头的任务
  233. new HideHeaderTask().execute();
  234. }
  235. break;
  236. }
  237. // 时刻记得更新下拉头中的信息
  238. if (currentStatus == STATUS_PULL_TO_REFRESH
  239. || currentStatus == STATUS_RELEASE_TO_REFRESH) {
  240. Log.d(TAG, "进入“时刻记得更新下拉头中的信息”的if语句");
  241. updateHeaderView();
  242. // 当前正处于下拉或释放状态,要让ListView失去焦点,否则被点击的那一项会一直处于选中状态
  243. listView.setPressed(false);
  244. listView.setFocusable(false);
  245. listView.setFocusableInTouchMode(false);
  246. lastStatus = currentStatus;
  247. // 当前正处于下拉或释放状态,通过返回true屏蔽掉ListView的滚动事件
  248. Log.d(TAG, "离开“时刻记得更新下拉头中的信息”的if语句");
  249. return true;
  250. }
  251. }
  252. Log.d(TAG, "离开onTouch");
  253. return false;
  254. }
  255. /**
  256. * 给下拉刷新控件注册一个监听器。 RefreshableView构造方法执行完成之后,紧接着就执行这个方法
  257. *
  258. * @param listener
  259. * 监听器的实现。
  260. * @param id
  261. * 为了防止不同界面的下拉刷新在上次更新时间上互相有冲突, 请不同界面在注册下拉刷新监听器时一定要传入不同的id。
  262. */
  263. public void setOnRefreshListener(PullToRefreshListener listener, int id) {
  264. Log.d(TAG, "进入setOnRefreshListener方法");
  265. mListener = listener;
  266. Log.d(TAG, "离开setOnRefreshListener方法");
  267. mId = id;
  268. }
  269. /**
  270. * 当所有的刷新逻辑完成后,记录调用一下,否则你的ListView将一直处于正在刷新状态。
  271. */
  272. public void finishRefreshing() {
  273. Log.d(TAG, "进入finishRefreshing方法");
  274. currentStatus = STATUS_REFRESH_FINISHED;
  275. preferences.edit()
  276. .putLong(UPDATED_AT + mId, System.currentTimeMillis()).commit();
  277. new HideHeaderTask().execute();
  278. Log.d(TAG, "离开finishRefreshing方法");
  279. }
  280. /**
  281. * 根据当前ListView的滚动状态来设定ableToPull的值,
  282. * 每次都需要在onTouch中第一个执行,这样可以判断出当前应该是滚动ListView,还是应该进行下拉。
  283. */
  284. private void setIsAbleToPull(MotionEvent event) {
  285. Log.d(TAG, "进入setIsAbleToPull方法");
  286. View firstChild = listView.getChildAt(0);
  287. if (firstChild != null) {
  288. int firstVisiblePos = listView.getFirstVisiblePosition();
  289. if (firstVisiblePos == 0 && firstChild.getTop() == 0) {
  290. if (!ableToPull) {
  291. yDown = event.getRawY();
  292. }
  293. // 如果首个元素的上边缘,距离父布局值为0,就说明ListView滚动到了最顶部,此时应该允许下拉刷新
  294. ableToPull = true;
  295. } else {
  296. if (headerLayoutParams.topMargin != hideHeaderHeight) {
  297. headerLayoutParams.topMargin = hideHeaderHeight;
  298. header.setLayoutParams(headerLayoutParams);
  299. }
  300. ableToPull = false;
  301. }
  302. } else {
  303. // 如果ListView中没有元素,也应该允许下拉刷新
  304. ableToPull = true;
  305. }
  306. Log.d(TAG, "离开setIsAbleToPull方法");
  307. }
  308. /**
  309. * 更新下拉头中的信息。
  310. */
  311. private void updateHeaderView() {
  312. Log.d(TAG, "进入updateHeaderView方法");
  313. if (lastStatus != currentStatus) {
  314. if (currentStatus == STATUS_PULL_TO_REFRESH) {
  315. description.setText(getResources().getString(
  316. R.string.pull_to_refresh));
  317. arrow.setVisibility(View.VISIBLE);
  318. progressBar.setVisibility(View.GONE);
  319. rotateArrow();
  320. } else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
  321. description.setText(getResources().getString(
  322. R.string.release_to_refresh));
  323. arrow.setVisibility(View.VISIBLE);
  324. progressBar.setVisibility(View.GONE);
  325. rotateArrow();
  326. } else if (currentStatus == STATUS_REFRESHING) {
  327. description.setText(getResources().getString(
  328. R.string.refreshing));
  329. progressBar.setVisibility(View.VISIBLE);
  330. arrow.clearAnimation();
  331. arrow.setVisibility(View.GONE);
  332. }
  333. refreshUpdatedAtValue();
  334. Log.d(TAG, "离开updateHeaderView方法");
  335. }
  336. }
  337. /**
  338. * 根据当前的状态来旋转箭头。
  339. */
  340. private void rotateArrow() {
  341. Log.d(TAG, "进入rotateArrow方法");
  342. float pivotX = arrow.getWidth() / 2f;
  343. float pivotY = arrow.getHeight() / 2f;
  344. float fromDegrees = 0f;
  345. float toDegrees = 0f;
  346. if (currentStatus == STATUS_PULL_TO_REFRESH) {
  347. fromDegrees = 180f;
  348. toDegrees = 360f;
  349. } else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
  350. fromDegrees = 0f;
  351. toDegrees = 180f;
  352. }
  353. RotateAnimation animation = new RotateAnimation(fromDegrees, toDegrees,
  354. pivotX, pivotY);
  355. animation.setDuration(100);
  356. animation.setFillAfter(true);
  357. arrow.startAnimation(animation);
  358. Log.d(TAG, "离开rotateArrow方法");
  359. }
  360. /**
  361. * 刷新下拉头中上次更新时间的文字描述。
  362. */
  363. private void refreshUpdatedAtValue() {
  364. Log.d(TAG, "进入refreshUpdatedAtValue方法");
  365. /**
  366. * 从SharedPreferences存储文件中取出上次保存的更新时间
  367. */
  368. lastUpdateTime = preferences.getLong(UPDATED_AT + mId, -1);
  369. /**
  370. * 获得当前时间
  371. */
  372. long currentTime = System.currentTimeMillis();
  373. /**
  374. * 获得当前更新时间和上次更新时间的时间差
  375. */
  376. long timePassed = currentTime - lastUpdateTime;
  377. long timeIntoFormat;
  378. /**
  379. * 此次更新显示的提示信息(如,暂未更新过,或者,5分钟前更新过)
  380. */
  381. String updateAtValue;
  382. if (lastUpdateTime == -1) {
  383. // lastUpdateTime =-1指的是还没有更新(暂未更新过),否则就是更新过,判断具体更新的时间
  384. /**
  385. * getResources()得到Resources对象 ,通过该对象可以获得res文件夹下所有的资源,如图片,字符串,布局文件
  386. */
  387. updateAtValue = getResources().getString(R.string.not_updated_yet);
  388. } else if (timePassed < 0) {
  389. updateAtValue = getResources().getString(R.string.time_error);
  390. } else if (timePassed < ONE_MINUTE) {
  391. updateAtValue = getResources().getString(R.string.updated_just_now);
  392. } else if (timePassed < ONE_HOUR) {
  393. /**
  394. * %n$ms:代表输出的是字符串,n代表是第几个参数,设置m的值可以在输出之前放置空格 也可简单写成: %s (表示字符串)【
  395. * 扩展:%d (表示整数) %f (表示浮点数)】 String str = "上次更新于%1$s前" ; String
  396. * string= String.format(str,"23分钟") 就是指将字符串中,第一个要替换的字符,替换为23
  397. * string的结果就是:上次更新于23分钟前
  398. */
  399. timeIntoFormat = timePassed / ONE_MINUTE;
  400. String value = timeIntoFormat + "分钟";
  401. updateAtValue = String.format(
  402. getResources().getString(R.string.updated_at), value);
  403. } else if (timePassed < ONE_DAY) {
  404. timeIntoFormat = timePassed / ONE_HOUR;
  405. String value = timeIntoFormat + "小时";
  406. updateAtValue = String.format(
  407. getResources().getString(R.string.updated_at), value);
  408. } else if (timePassed < ONE_MONTH) {
  409. timeIntoFormat = timePassed / ONE_DAY;
  410. String value = timeIntoFormat + "天";
  411. updateAtValue = String.format(
  412. getResources().getString(R.string.updated_at), value);
  413. } else if (timePassed < ONE_YEAR) {
  414. timeIntoFormat = timePassed / ONE_MONTH;
  415. String value = timeIntoFormat + "个月";
  416. updateAtValue = String.format(
  417. getResources().getString(R.string.updated_at), value);
  418. } else {
  419. timeIntoFormat = timePassed / ONE_YEAR;
  420. String value = timeIntoFormat + "年";
  421. updateAtValue = String.format(
  422. getResources().getString(R.string.updated_at), value);
  423. }
  424. updateAt.setText(updateAtValue);
  425. Log.d(TAG, "离开refreshUpdatedAtValue方法");
  426. }
  427. /**
  428. * 正在刷新的任务,在此任务中会去回调注册进来的下拉刷新监听器。
  429. */
  430. class RefreshingTask extends AsyncTask<Void, Integer, Void> {
  431. @Override
  432. protected Void doInBackground(Void... params) {
  433. Log.d(TAG, "---进入RefreshingTask的doInBackground方法");
  434. int topMargin = headerLayoutParams.topMargin;
  435. while (true) {
  436. topMargin = topMargin + SCROLL_SPEED;
  437. if (topMargin <= 0) {
  438. topMargin = 0;
  439. break;
  440. }
  441. publishProgress(topMargin);
  442. sleep(10);
  443. }
  444. currentStatus = STATUS_REFRESHING;
  445. /**
  446. * 调用publishProgress(0);方法后,紧接着会执行onProgressUpdate这个方法
  447. */
  448. publishProgress(0);
  449. if (mListener != null) {
  450. mListener.onRefresh();
  451. }
  452. Log.d(TAG, "离开doInBackground方法");
  453. return null;
  454. }
  455. @Override
  456. protected void onProgressUpdate(Integer... topMargin) {
  457. Log.d(TAG, "---进入RefreshingTask的onProgressUpdate方法");
  458. updateHeaderView();
  459. headerLayoutParams.topMargin = topMargin[0];
  460. header.setLayoutParams(headerLayoutParams);
  461. Log.d(TAG, "离开onProgressUpdate方法");
  462. }
  463. @Override
  464. protected void onPostExecute(Void result) {
  465. // TODO Auto-generated method stub
  466. Log.d(TAG, "---进入RefreshingTask的onPostExecute方法");
  467. /**
  468. * 向adapter适配器中添加新的数据
  469. */
  470. if (adapter != null) {
  471. adapter.insert("新数据_" + count++, 0);
  472. }
  473. }
  474. }
  475. /**
  476. * 隐藏下拉头的任务,当未进行下拉刷新或下拉刷新完成后,此任务将会使下拉头重新隐藏。
  477. */
  478. class HideHeaderTask extends AsyncTask<Void, Integer, Integer> {
  479. @Override
  480. protected Integer doInBackground(Void... params) {
  481. Log.d(TAG, "------进入HideHeaderTask的doInBackground方法");
  482. int topMargin = headerLayoutParams.topMargin;
  483. while (true) {
  484. topMargin = topMargin + SCROLL_SPEED;
  485. if (topMargin <= hideHeaderHeight) {
  486. topMargin = hideHeaderHeight;
  487. break;
  488. }
  489. publishProgress(topMargin);
  490. sleep(10);
  491. }
  492. Log.d(TAG, "离开doInBackground方法");
  493. return topMargin;
  494. }
  495. @Override
  496. protected void onProgressUpdate(Integer... topMargin) {
  497. Log.d(TAG, "------进入HideHeaderTask的onProgressUpdate方法");
  498. headerLayoutParams.topMargin = topMargin[0];
  499. header.setLayoutParams(headerLayoutParams);
  500. Log.d(TAG, "离开onProgressUpdate方法");
  501. }
  502. @Override
  503. protected void onPostExecute(Integer topMargin) {
  504. Log.d(TAG, "-----进入HideHeaderTask的onPostExecute方法");
  505. /**
  506. * 刷新适配器中数据
  507. */
  508. if (adapter != null) {
  509. adapter.notifyDataSetChanged();
  510. }
  511. headerLayoutParams.topMargin = topMargin;
  512. header.setLayoutParams(headerLayoutParams);
  513. currentStatus = STATUS_REFRESH_FINISHED;
  514. Log.d(TAG, "离开onPostExecute方法");
  515. }
  516. }
  517. /**
  518. * 使当前线程睡眠指定的毫秒数。
  519. *
  520. * @param time
  521. * 指定当前线程睡眠多久,以毫秒为单位
  522. */
  523. private void sleep(int time) {
  524. try {
  525. Thread.sleep(time);
  526. } catch (InterruptedException e) {
  527. e.printStackTrace();
  528. }
  529. }
  530. /**
  531. * 下拉刷新的监听器,使用下拉刷新的地方应该注册此监听器来获取刷新回调。
  532. */
  533. public interface PullToRefreshListener {
  534. /**
  535. * 刷新时会去回调此方法,在方法内编写具体的刷新逻辑。注意此方法是在子线程中调用的, 你可以不必另开线程来进行耗时操作。
  536. */
  537. void onRefresh();
  538. }
  539. /**
  540. * 将adapter对象传到refreshableView中,方便更新新数据到适配器中
  541. * @param adapter
  542. */
  543. public void initAdapter(ArrayAdapter<String> adapter) {
  544. // TODO Auto-generated method stub
  545. this.adapter = adapter;
  546. }
  547. }

下拉刷新原理:

  1. /**
  2. * 首先在RefreshableView的构造函数中动态添加了刚刚定义的pull_refresh这个布局作为下拉头,
  3. * 然后在onLayout方法中将下拉头向上偏移出了屏幕,
  4. * 再给ListView注册了touch事件。
  5. * 之后每当手指在ListView上滑动时,onTouch方法就会执行。
  6. * 在onTouch方法中的第一行就调用了setIsAbleToPull方法来判断ListView是否滚动到了最顶部,
  7. * 只有滚动到了最顶部才会执行后面的代码,否则就视为正常的ListView滚动,不做任何处理。
  8. * 当ListView滚动到了最顶部时,如果手指还在向下拖动,就会改变下拉头的偏移值,
  9. * 让下拉头显示出来,下拉的距离设定为手指移动距离的1/2,这样才会有拉力的感觉。
  10. * 如果下拉的距离足够大,在松手的时候就会执行刷新操作,如果距离不够大,就仅仅重新隐藏下拉头。
  11. * 具体的刷新操作会在RefreshingTask中进行,
  12. * 其中在doInBackground方法中回调了PullToRefreshListener接口的onRefresh方法,
  13. * 这也是大家在使用RefreshableView时必须要去实现的一个接口,
  14. * 因为具体刷新的逻辑就应该写在onRefresh方法中,后面会演示使用的方法。
  15. * 另外每次在下拉的时候都还会调用updateHeaderView方法来改变下拉头中的数据,
  16. * 比如箭头方向的旋转,下拉文字描述的改变等。
  17. *
  18. */

项目下载:

android下拉刷新功能demo下载

发表评论

表情:
评论列表 (有 0 条评论,317人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Android ListView刷新

    ListView的重要性我就不多说了,下拉刷新的功能以前一直用的别人的,这两天参考一些资料自己写了个ListView下拉刷新的控件,自己会写才能掌握在自己手里,以后扩展什么的,