Android下拉刷新完全解析


首先讲一下实现原理。这里我们将采取的方案是使用组合View的方式,先自定义一个布局继承自LinearLayout,然后在这个布局中加入下拉头和ListView这两个子元素,并让这两个子元素纵向排列。初始化的时候,让下拉头向上偏移出屏幕,这样我们看到的就只有ListView了。然后对ListView的touch事件进行监听,如果当前ListView已经滚动到顶部并且手指还在向下拉的话,那就将下拉头显示出来,松手后进行刷新操作,并将下拉头隐藏。原理示意图如下:

\

那我们现在就来动手实现一下,新建一个项目起名叫PullToRefreshTest,先在项目中定义一个下拉头的布局文件pull_to_refresh.xml,代码如下所示:

[html] view plaincopy
  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. >
  16. <imageview class="alt"
  17. android:id="@+id/arrow"
  18. android:layout_width="wrap_content"
  19. android:layout_height="wrap_content"
  20. android:layout_centerInParent="true"
  21. android:src="@drawable/arrow"
  22. />
  23. <progressbar
  24. android:id="@+id/progress_bar"
  25. android:layout_width="30dip"
  26. android:layout_height="30dip"
  27. android:layout_centerInParent="true"
  28. android:visibility="gone"
  29. />
  30. <linearlayout class="alt"
  31. android:layout_width="0dip"
  32. android:layout_height="60dip"
  33. android:layout_weight="12"
  34. android:orientation="vertical" >
  35. <textview class="alt"
  36. android:id="@+id/description"
  37. android:layout_width="fill_parent"
  38. android:layout_height="0dip"
  39. android:layout_weight="1"
  40. android:gravity="center_horizontal|bottom"
  41. android:text="@string/pull_to_refresh" />
  42. <textview class="alt"
  43. android:id="@+id/updated_at"
  44. android:layout_width="fill_parent"
  45. android:layout_height="0dip"
  46. android:layout_weight="1"
  47. android:gravity="center_horizontal|top"
  48. android:text="@string/updated_at" />
  49. 在这个布局中,我们包含了一个下拉指示箭头,一个下拉状态文字提示,和一个上次更新的时间。当然,还有一个隐藏的旋转进度条,只有正在刷新的时候我们才会将它显示出来。
    布局中所有引用的字符串我们都放在strings.xml中,如下所示: [html] view plaincopy
    1. PullToRefreshTest
    2. 下拉可以刷新
    3. 释放立即刷新
    4. 正在刷新…
    5. 暂未更新过
    6. 上次更新于%1$s前
    7. 刚刚更新
    8. 时间有问题
    9. 然后新建一个RefreshableView继承自LinearLayout,代码如下所示: [java] view plaincopy
      1. public class RefreshableView extends LinearLayout implements OnTouchListener {
      2. /**
      3. * 下拉状态
      4. */
      5. public static final int STATUS_PULL_TO_REFRESH = 0;
      6. /**
      7. * 释放立即刷新状态
      8. */
      9. public static final int STATUS_RELEASE_TO_REFRESH = 1;
      10. /**
      11. * 正在刷新状态
      12. */
      13. public static final int STATUS_REFRESHING = 2;
      14. /**
      15. * 刷新完成或未刷新状态
      16. */
      17. public static final int STATUS_REFRESH_FINISHED = 3;
      18. /**
      19. * 下拉头部回滚的速度
      20. */
      21. public static final int SCROLL_SPEED = -20;
      22. /**
      23. * 一分钟的毫秒值,用于判断上次的更新时间
      24. */
      25. public static final long ONE_MINUTE = 60 * 1000;
      26. /**
      27. * 一小时的毫秒值,用于判断上次的更新时间
      28. */
      29. public static final long ONE_HOUR = 60 * ONE_MINUTE;
      30. /**
      31. * 一天的毫秒值,用于判断上次的更新时间
      32. */
      33. public static final long ONE_DAY = 24 * ONE_HOUR;
      34. /**
      35. * 一月的毫秒值,用于判断上次的更新时间
      36. */
      37. public static final long ONE_MONTH = 30 * ONE_DAY;
      38. /**
      39. * 一年的毫秒值,用于判断上次的更新时间
      40. */
      41. public static final long ONE_YEAR = 12 * ONE_MONTH;
      42. /**
      43. * 上次更新时间的字符串常量,用于作为SharedPreferences的键值
      44. */
      45. private static final String UPDATED_AT = "updated_at";
      46. /**
      47. * 下拉刷新的回调接口
      48. */
      49. private PullToRefreshListener mListener;
      50. /**
      51. * 用于存储上次更新时间
      52. */
      53. private SharedPreferences preferences;
      54. /**
      55. * 下拉头的View
      56. */
      57. private View header;
      58. /**
      59. * 需要去下拉刷新的ListView
      60. */
      61. private ListView listView;
      62. /**
      63. * 刷新时显示的进度条
      64. */
      65. private ProgressBar progressBar;
      66. /**
      67. * 指示下拉和释放的箭头
      68. */
      69. private ImageView arrow;
      70. /**
      71. * 指示下拉和释放的文字描述
      72. */
      73. private TextView description;
      74. /**
      75. * 上次更新时间的文字描述
      76. */
      77. private TextView updateAt;
      78. /**
      79. * 下拉头的布局参数
      80. */
      81. private MarginLayoutParams headerLayoutParams;
      82. /**
      83. * 上次更新时间的毫秒值
      84. */
      85. private long lastUpdateTime;
      86. /**
      87. * 为了防止不同界面的下拉刷新在上次更新时间上互相有冲突,使用id来做区分
      88. */
      89. private int mId = -1;
      90. /**
      91. * 下拉头的高度
      92. */
      93. private int hideHeaderHeight;
      94. /**
      95. * 当前处理什么状态,可选值有STATUS_PULL_TO_REFRESH, STATUS_RELEASE_TO_REFRESH,
      96. * STATUS_REFRESHING 和 STATUS_REFRESH_FINISHED
      97. */
      98. private int currentStatus = STATUS_REFRESH_FINISHED;;
      99. /**
      100. * 记录上一次的状态是什么,避免进行重复操作
      101. */
      102. private int lastStatus = currentStatus;
      103. /**
      104. * 手指按下时的屏幕纵坐标
      105. */
      106. private float yDown;
      107. /**
      108. * 在被判定为滚动之前用户手指可以移动的最大值。
      109. */
      110. private int touchSlop;
      111. /**
      112. * 是否已加载过一次layout,这里onLayout中的初始化只需加载一次
      113. */
      114. private boolean loadOnce;
      115. /**
      116. * 当前是否可以下拉,只有ListView滚动到头的时候才允许下拉
      117. */
      118. private boolean ableToPull;
      119. /**
      120. * 下拉刷新控件的构造函数,会在运行时动态添加一个下拉头的布局。
      121. *
      122. * @param context
      123. * @param attrs
      124. */
      125. public RefreshableView(Context context, AttributeSet attrs) {
      126. super(context, attrs);
      127. preferences = PreferenceManager.getDefaultSharedPreferences(context);
      128. header = LayoutInflater.from(context).inflate(R.layout.pull_to_refresh, null, true);
      129. progressBar = (ProgressBar) header.findViewById(R.id.progress_bar);
      130. arrow = (ImageView) header.findViewById(R.id.arrow);
      131. description = (TextView) header.findViewById(R.id.description);
      132. updateAt = (TextView) header.findViewById(R.id.updated_at);
      133. touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
      134. refreshUpdatedAtValue();
      135. setOrientation(VERTICAL);
      136. addView(header, 0);
      137. }
      138. /**
      139. * 进行一些关键性的初始化操作,比如:将下拉头向上偏移进行隐藏,给ListView注册touch事件。
      140. */
      141. @Override
      142. protected void onLayout(boolean changed, int l, int t, int r, int b) {
      143. super.onLayout(changed, l, t, r, b);
      144. if (changed && !loadOnce) {
      145. hideHeaderHeight = -header.getHeight();
      146. headerLayoutParams = (MarginLayoutParams) header.getLayoutParams();
      147. headerLayoutParams.topMargin = hideHeaderHeight;
      148. header.setLayoutParams(headerLayoutParams);
      149. listView = (ListView) getChildAt(1);
      150. listView.setOnTouchListener(this);
      151. loadOnce = true;
      152. }
      153. }
      154. /**
      155. * 当ListView被触摸时调用,其中处理了各种下拉刷新的具体逻辑。
      156. */
      157. @Override
      158. public boolean onTouch(View v, MotionEvent event) {
      159. setIsAbleToPull(event);
      160. if (ableToPull) {
      161. switch (event.getAction()) {
      162. case MotionEvent.ACTION_DOWN:
      163. yDown = event.getRawY();
      164. break;
      165. case MotionEvent.ACTION_MOVE:
      166. float yMove = event.getRawY();
      167. int distance = (int) (yMove - yDown);
      168. // 如果手指是下滑状态,并且下拉头是完全隐藏的,就屏蔽下拉事件
      169. if (distance <= 0 && headerLayoutParams.topMargin <= hideHeaderHeight) {
      170. return false;
      171. }
      172. if (distance < touchSlop) {
      173. return false;
      174. }
      175. if (currentStatus != STATUS_REFRESHING) {
      176. if (headerLayoutParams.topMargin > 0) {
      177. currentStatus = STATUS_RELEASE_TO_REFRESH;
      178. } else {
      179. currentStatus = STATUS_PULL_TO_REFRESH;
      180. }
      181. // 通过偏移下拉头的topMargin值,来实现下拉效果
      182. headerLayoutParams.topMargin = (distance / 2) + hideHeaderHeight;
      183. header.setLayoutParams(headerLayoutParams);
      184. }
      185. break;
      186. case MotionEvent.ACTION_UP:
      187. default:
      188. if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
      189. // 松手时如果是释放立即刷新状态,就去调用正在刷新的任务
      190. new RefreshingTask().execute();
      191. } else if (currentStatus == STATUS_PULL_TO_REFRESH) {
      192. // 松手时如果是下拉状态,就去调用隐藏下拉头的任务
      193. new HideHeaderTask().execute();
      194. }
      195. break;
      196. }
      197. // 时刻记得更新下拉头中的信息
      198. if (currentStatus == STATUS_PULL_TO_REFRESH
      199. || currentStatus == STATUS_RELEASE_TO_REFRESH) {
      200. updateHeaderView();
      201. // 当前正处于下拉或释放状态,要让ListView失去焦点,否则被点击的那一项会一直处于选中状态
      202. listView.setPressed(false);
      203. listView.setFocusable(false);
      204. listView.setFocusableInTouchMode(false);
      205. lastStatus = currentStatus;
      206. // 当前正处于下拉或释放状态,通过返回true屏蔽掉ListView的滚动事件
      207. return true;
      208. }
      209. }
      210. return false;
      211. }
      212. /**
      213. * 给下拉刷新控件注册一个监听器。
      214. *
      215. * @param listener
      216. * 监听器的实现。
      217. * @param id
      218. * 为了防止不同界面的下拉刷新在上次更新时间上互相有冲突, 请不同界面在注册下拉刷新监听器时一定要传入不同的id。
      219. */
      220. public void setOnRefreshListener(PullToRefreshListener listener, int id) {
      221. mListener = listener;
      222. mId = id;
      223. }
      224. /**
      225. * 当所有的刷新逻辑完成后,记录调用一下,否则你的ListView将一直处于正在刷新状态。
      226. */
      227. public void finishRefreshing() {
      228. currentStatus = STATUS_REFRESH_FINISHED;
      229. preferences.edit().putLong(UPDATED_AT + mId, System.currentTimeMillis()).commit();
      230. new HideHeaderTask().execute();
      231. }
      232. /**
      233. * 根据当前ListView的滚动状态来设定 {@link #ableToPull}
      234. * 的值,每次都需要在onTouch中第一个执行,这样可以判断出当前应该是滚动ListView,还是应该进行下拉。
      235. *
      236. * @param event
      237. */
      238. private void setIsAbleToPull(MotionEvent event) {
      239. View firstChild = listView.getChildAt(0);
      240. if (firstChild != null) {
      241. int firstVisiblePos = listView.getFirstVisiblePosition();
      242. if (firstVisiblePos == 0 && firstChild.getTop() == 0) {
      243. if (!ableToPull) {
      244. yDown = event.getRawY();
      245. }
      246. // 如果首个元素的上边缘,距离父布局值为0,就说明ListView滚动到了最顶部,此时应该允许下拉刷新
      247. ableToPull = true;
      248. } else {
      249. if (headerLayoutParams.topMargin != hideHeaderHeight) {
      250. headerLayoutParams.topMargin = hideHeaderHeight;
      251. header.setLayoutParams(headerLayoutParams);
      252. }
      253. ableToPull = false;
      254. }
      255. } else {
      256. // 如果ListView中没有元素,也应该允许下拉刷新
      257. ableToPull = true;
      258. }
      259. }
      260. /**
      261. * 更新下拉头中的信息。
      262. */
      263. private void updateHeaderView() {
      264. if (lastStatus != currentStatus) {
      265. if (currentStatus == STATUS_PULL_TO_REFRESH) {
      266. description.setText(getResources().getString(R.string.pull_to_refresh));
      267. arrow.setVisibility(View.VISIBLE);
      268. progressBar.setVisibility(View.GONE);
      269. rotateArrow();
      270. } else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
      271. description.setText(getResources().getString(R.string.release_to_refresh));
      272. arrow.setVisibility(View.VISIBLE);
      273. progressBar.setVisibility(View.GONE);
      274. rotateArrow();
      275. } else if (currentStatus == STATUS_REFRESHING) {
      276. description.setText(getResources().getString(R.string.refreshing));
      277. progressBar.setVisibility(View.VISIBLE);
      278. arrow.clearAnimation();
      279. arrow.setVisibility(View.GONE);
      280. }
      281. refreshUpdatedAtValue();
      282. }
      283. }
      284. /**
      285. * 根据当前的状态来旋转箭头。
      286. */
      287. private void rotateArrow() {
      288. float pivotX = arrow.getWidth() / 2f;
      289. float pivotY = arrow.getHeight() / 2f;
      290. float fromDegrees = 0f;
      291. float toDegrees = 0f;
      292. if (currentStatus == STATUS_PULL_TO_REFRESH) {
      293. fromDegrees = 180f;
      294. toDegrees = 360f;
      295. } else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
      296. fromDegrees = 0f;
      297. toDegrees = 180f;
      298. }
      299. RotateAnimation animation = new RotateAnimation(fromDegrees, toDegrees, pivotX, pivotY);
      300. animation.setDuration(100);
      301. animation.setFillAfter(true);
      302. arrow.startAnimation(animation);
      303. }
      304. /**
      305. * 刷新下拉头中上次更新时间的文字描述。
      306. */
      307. private void refreshUpdatedAtValue() {
      308. lastUpdateTime = preferences.getLong(UPDATED_AT + mId, -1);
      309. long currentTime = System.currentTimeMillis();
      310. long timePassed = currentTime - lastUpdateTime;
      311. long timeIntoFormat;
      312. String updateAtValue;
      313. if (lastUpdateTime == -1) {
      314. updateAtValue = getResources().getString(R.string.not_updated_yet);
      315. } else if (timePassed < 0) {
      316. updateAtValue = getResources().getString(R.string.time_error);
      317. } else if (timePassed < ONE_MINUTE) {
      318. updateAtValue = getResources().getString(R.string.updated_just_now);
      319. } else if (timePassed < ONE_HOUR) {
      320. timeIntoFormat = timePassed / ONE_MINUTE;
      321. String value = timeIntoFormat + "分钟";
      322. updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
      323. } else if (timePassed < ONE_DAY) {
      324. timeIntoFormat = timePassed / ONE_HOUR;
      325. String value = timeIntoFormat + "小时";
      326. updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
      327. } else if (timePassed < ONE_MONTH) {
      328. timeIntoFormat = timePassed / ONE_DAY;
      329. String value = timeIntoFormat + "天";
      330. updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
      331. } else if (timePassed < ONE_YEAR) {
      332. timeIntoFormat = timePassed / ONE_MONTH;
      333. String value = timeIntoFormat + "个月";
      334. updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
      335. } else {
      336. timeIntoFormat = timePassed / ONE_YEAR;
      337. String value = timeIntoFormat + "年";
      338. updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
      339. }
      340. updateAt.setText(updateAtValue);
      341. }
      342. /**
      343. * 正在刷新的任务,在此任务中会去回调注册进来的下拉刷新监听器。
      344. *
      345. * @author guolin
      346. */
      347. class RefreshingTask extends AsyncTask {
      348. @Override
      349. protected Void doInBackground(Void... params) {
      350. int topMargin = headerLayoutParams.topMargin;
      351. while (true) {
      352. topMargin = topMargin + SCROLL_SPEED;
      353. if (topMargin <= 0) {
      354. topMargin = 0;
      355. break;
      356. }
      357. publishProgress(topMargin);
      358. sleep(10);
      359. }
      360. currentStatus = STATUS_REFRESHING;
      361. publishProgress(0);
      362. if (mListener != null) {
      363. mListener.onRefresh();
      364. }
      365. return null;
      366. }
      367. @Override
      368. protected void onProgressUpdate(Integer... topMargin) {
      369. updateHeaderView();
      370. headerLayoutParams.topMargin = topMargin[0];
      371. header.setLayoutParams(headerLayoutParams);
      372. }
      373. }
      374. /**
      375. * 隐藏下拉头的任务,当未进行下拉刷新或下拉刷新完成后,此任务将会使下拉头重新隐藏。
      376. *
      377. * @author guolin
      378. */
      379. class HideHeaderTask extends AsyncTask {
      380. @Override
      381. protected Integer doInBackground(Void... params) {
      382. int topMargin = headerLayoutParams.topMargin;
      383. while (true) {
      384. topMargin = topMargin + SCROLL_SPEED;
      385. if (topMargin <= hideHeaderHeight) {
      386. topMargin = hideHeaderHeight;
      387. break;
      388. }
      389. publishProgress(topMargin);
      390. sleep(10);
      391. }
      392. return topMargin;
      393. }
      394. @Override
      395. protected void onProgressUpdate(Integer... topMargin) {
      396. headerLayoutParams.topMargin = topMargin[0];
      397. header.setLayoutParams(headerLayoutParams);
      398. }
      399. @Override
      400. protected void onPostExecute(Integer topMargin) {
      401. headerLayoutParams.topMargin = topMargin;
      402. header.setLayoutParams(headerLayoutParams);
      403. currentStatus = STATUS_REFRESH_FINISHED;
      404. }
      405. }
      406. /**
      407. * 使当前线程睡眠指定的毫秒数。
      408. *
      409. * @param time
      410. * 指定当前线程睡眠多久,以毫秒为单位
      411. */
      412. private void sleep(int time) {
      413. try {
      414. Thread.sleep(time);
      415. } catch (InterruptedException e) {
      416. e.printStackTrace();
      417. }
      418. }
      419. /**
      420. * 下拉刷新的监听器,使用下拉刷新的地方应该注册此监听器来获取刷新回调。
      421. *
      422. * @author guolin
      423. */
      424. public interface PullToRefreshListener {
      425. /**
      426. * 刷新时会去回调此方法,在方法内编写具体的刷新逻辑。注意此方法是在子线程中调用的, 你可以不必另开线程来进行耗时操作。
      427. */
      428. void onRefresh();
      429. }
      430. }

        这个类是整个下拉刷新功能中最重要的一个类,注释已经写得比较详细了,我再简单解释一下。首先在RefreshableView的构造函数中动态添加了刚刚定义的pull_to_refresh这个布局作为下拉头,然后在onLayout方法中将下拉头向上偏移出了屏幕,再给ListView注册了touch事件。之后每当手指在ListView上滑动时,onTouch方法就会执行。在onTouch方法中的第一行就调用了setIsAbleToPull方法来判断ListView是否滚动到了最顶部,只有滚动到了最顶部才会执行后面的代码,否则就视为正常的ListView滚动,不做任何处理。当ListView滚动到了最顶部时,如果手指还在向下拖动,就会改变下拉头的偏移值,让下拉头显示出来,下拉的距离设定为手指移动距离的1/2,这样才会有拉力的感觉。如果下拉的距离足够大,在松手的时候就会执行刷新操作,如果距离不够大,就仅仅重新隐藏下拉头。

        具体的刷新操作会在RefreshingTask中进行,其中在doInBackground方法中回调了PullToRefreshListener接口的onRefresh方法,这也是大家在使用RefreshableView时必须要去实现的一个接口,因为具体刷新的逻辑就应该写在onRefresh方法中,后面会演示使用的方法。

        另外每次在下拉的时候都还会调用updateHeaderView方法来改变下拉头中的数据,比如箭头方向的旋转,下拉文字描述的改变等。更加深入的理解请大家仔细去阅读RefreshableView中的代码。
        现在我们已经把下拉刷新的所有功能都完成了,接下来就要看一看如何在项目中引入下拉刷新了。打开或新建activity_main.xml作为程序主界面的布局,加入如下代码: [html] view plaincopy
        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. tools:context=".MainActivity" >
        6. <com.example.pulltorefreshtest.refreshableview
        7. android:id="@+id/refreshable_view"
        8. android:layout_width="fill_parent"
        9. android:layout_height="fill_parent" >
        10. <listview class="alt"
        11. android:id="@+id/list_view"
        12. android:layout_width="fill_parent"
        13. android:layout_height="fill_parent" >
        14. 可以看到,我们在自定义的RefreshableView中加入了一个ListView,这就意味着给这个ListView加入了下拉刷新的功能,就是这么简单!
          然后我们再来看一下程序的主Activity,打开或新建MainActivity,加入如下代码: [java] view plaincopy
          1. public class MainActivity extends Activity {
          2. RefreshableView refreshableView;
          3. ListView listView;
          4. ArrayAdapter adapter;
          5. String[] items = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L" };
          6. @Override
          7. protected void onCreate(Bundle savedInstanceState) {
          8. super.onCreate(savedInstanceState);
          9. requestWindowFeature(Window.FEATURE_NO_TITLE);
          10. setContentView(R.layout.activity_main);
          11. refreshableView = (RefreshableView) findViewById(R.id.refreshable_view);
          12. listView = (ListView) findViewById(R.id.list_view);
          13. adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, items);
          14. listView.setAdapter(adapter);
          15. refreshableView.setOnRefreshListener(new PullToRefreshListener() {
          16. @Override
          17. public void onRefresh() {
          18. try {
          19. Thread.sleep(3000);
          20. } catch (InterruptedException e) {
          21. e.printStackTrace();
          22. }
          23. refreshableView.finishRefreshing();
          24. }
          25. }, 0);
          26. }
          27. }

            可以看到,我们通过调用RefreshableView的setOnRefreshListener方法注册了一个监听器,当ListView正在刷新时就会回调监听器的onRefresh方法,刷新的具体逻辑就在这里处理。而且这个方法已经自动开启了线程,可以直接在onRefresh方法中进行耗时操作,比如向服务器请求最新数据等,在这里我就简单让线程睡眠3秒钟。另外在onRefresh方法的最后,一定要调用RefreshableView中的finishRefreshing方法,这个方法是用来通知RefreshableView刷新结束了,不然我们的ListView将一直处于正在刷新的状态。
            不知道大家有没有注意到,setOnRefreshListener这个方法其实是有两个参数的,我们刚刚也是传入了一个不起眼的0。那这第二个参数是用来做什么的呢?由于RefreshableView比较智能,它会自动帮我们记录上次刷新完成的时间,然后下拉的时候会在下拉头中显示距上次刷新已过了多久。这是一个非常好用的功能,让我们不用再自己手动去记录和计算时间了,但是却存在一个问题。如果当前我们的项目中有三个地方都使用到了下拉刷新的功能,现在在一处进行了刷新,其它两处的时间也都会跟着改变!因为刷新完成的时间是记录在配置文件中的,由于在一处刷新更改了配置文件,导致在其它两处读取到的配置文件时间已经是更改过的了。那解决方案是什么?就是每个用到下拉刷新的地方,给setOnRefreshListener方法的第二个参数中传入不同的id就行了。这样各处的上次刷新完成时间都是单独记录的,相互之间就不会再有影响。
            好了,全部的代码都在这里了,让我们来运行一下,看看效果吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值