关于listview的操作五花八门,有下拉刷新,分级显示,分页列表,逐页加载等,以后会陆续和大家分享这些技术,今天讲下下拉加载这个功能的实现。

最初的下拉加载应该是ios上的效果,现在很多应用如新浪微博等都加入了这个操作。即下拉listview刷新列表,这无疑是一个非常友好的操作。今天就和大家分享下这个操作的实现。

先看下运行效果:

   

 
 

代码参考国外朋友Johan Nilsson的实现,http://johannilsson.com/2011/03/13/android-pull-to-refresh-update.html

主要原理为监听触摸和滑动操作,在listview
头部加载一个视图。那要做的其实很简单:1.写好加载到listview头部的view
2.重写listview,实现onTouchEvent方法和onScroll方法,监听滑动状态。计算headview全部显示出来即可实行加载动
作,加载完成即刷新列表。重新隐藏headview。

首先写下headview的xml代码:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="fill_parent"
  3. android:layout_height="fill_parent"
  4. android:paddingTop="10dip"
  5. android:paddingBottom="15dip"
  6. android:gravity="center"
  7. android:id="@+id/pull_to_refresh_header"
  8. >
  9. <ProgressBar
  10. android:id="@+id/pull_to_refresh_progress"
  11. android:indeterminate="true"
  12. android:layout_width="wrap_content"
  13. android:layout_height="wrap_content"
  14. android:layout_marginLeft="30dip"
  15. android:layout_marginRight="20dip"
  16. android:layout_marginTop="10dip"
  17. android:visibility="gone"
  18. android:layout_centerVertical="true"
  19. style="?android:attr/progressBarStyleSmall"
  20. />
  21. <ImageView
  22. android:id="@+id/pull_to_refresh_image"
  23. android:layout_width="wrap_content"
  24. android:layout_height="wrap_content"
  25. android:layout_marginLeft="30dip"
  26. android:layout_marginRight="20dip"
  27. android:visibility="gone"
  28. android:layout_gravity="center"
  29. android:gravity="center"
  30. android:src="@drawable/ic_pulltorefresh_arrow"
  31. />
  32. <TextView
  33. android:id="@+id/pull_to_refresh_text"
  34. android:textAppearance="?android:attr/textAppearanceMedium"
  35. android:textStyle="bold"
  36. android:paddingTop="5dip"
  37. android:layout_width="fill_parent"
  38. android:layout_height="wrap_content"
  39. android:layout_gravity="center"
  40. android:gravity="center"
  41. />
  42. <TextView
  43. android:id="@+id/pull_to_refresh_updated_at"
  44. android:layout_below="@+id/pull_to_refresh_text"
  45. android:visibility="gone"
  46. android:textAppearance="?android:attr/textAppearanceSmall"
  47. android:layout_width="fill_parent"
  48. android:layout_height="wrap_content"
  49. android:layout_gravity="center"
  50. android:gravity="center"
  51. />
  52. </RelativeLayout>

代码比较简单,即headview包括一个进度条一个箭头和两段文字(一个显示加载状态,另一个显示最后刷新时间,本例就不设置了)。

而后重写listview,代码如下:

  1. package com.notice.pullrefresh;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.view.LayoutInflater;
  5. import android.view.MotionEvent;
  6. import android.view.View;
  7. import android.view.ViewGroup;
  8. import android.view.animation.LinearInterpolator;
  9. import android.view.animation.RotateAnimation;
  10. import android.widget.AbsListView;
  11. import android.widget.AbsListView.OnScrollListener;
  12. import android.widget.ImageView;
  13. import android.widget.ListAdapter;
  14. import android.widget.ListView;
  15. import android.widget.ProgressBar;
  16. import android.widget.RelativeLayout;
  17. import android.widget.TextView;
  18. public class PullToRefreshListView extends ListView implements OnScrollListener {
  19. // 状态
  20. private static final int TAP_TO_REFRESH = 1;
  21. private static final int PULL_TO_REFRESH = 2;
  22. private static final int RELEASE_TO_REFRESH = 3;
  23. private static final int REFRESHING = 4;
  24. private OnRefreshListener mOnRefreshListener;
  25. // 监听对listview的滑动动作
  26. private OnScrollListener mOnScrollListener;
  27. private LayoutInflater mInflater;
  28. //顶部刷新时出现的控件
  29. private RelativeLayout mRefreshView;
  30. private TextView mRefreshViewText;
  31. private ImageView mRefreshViewImage;
  32. private ProgressBar mRefreshViewProgress;
  33. private TextView mRefreshViewLastUpdated;
  34. // 当前滑动状态
  35. private int mCurrentScrollState;
  36. // 当前刷新状态
  37. private int mRefreshState;
  38. // 箭头动画效果
  39. private RotateAnimation mFlipAnimation;
  40. private RotateAnimation mReverseFlipAnimation;
  41. private int mRefreshViewHeight;
  42. private int mRefreshOriginalTopPadding;
  43. private int mLastMotionY;
  44. private boolean mBounceHack;
  45. public PullToRefreshListView(Context context) {
  46. super(context);
  47. init(context);
  48. }
  49. public PullToRefreshListView(Context context, AttributeSet attrs) {
  50. super(context, attrs);
  51. init(context);
  52. }
  53. public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {
  54. super(context, attrs, defStyle);
  55. init(context);
  56. }
  57. /**
  58. * 初始化控件和箭头动画(这里直接在代码中初始化动画而不是通过xml)
  59. */
  60. private void init(Context context) {
  61. mFlipAnimation = new RotateAnimation(0, -180,
  62. RotateAnimation.RELATIVE_TO_SELF, 0.5f,
  63. RotateAnimation.RELATIVE_TO_SELF, 0.5f);
  64. mFlipAnimation.setInterpolator(new LinearInterpolator());
  65. mFlipAnimation.setDuration(250);
  66. mFlipAnimation.setFillAfter(true);
  67. mReverseFlipAnimation = new RotateAnimation(-180, 0,
  68. RotateAnimation.RELATIVE_TO_SELF, 0.5f,
  69. RotateAnimation.RELATIVE_TO_SELF, 0.5f);
  70. mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
  71. mReverseFlipAnimation.setDuration(250);
  72. mReverseFlipAnimation.setFillAfter(true);
  73. mInflater = (LayoutInflater) context.getSystemService(
  74. Context.LAYOUT_INFLATER_SERVICE);
  75. mRefreshView = (RelativeLayout) mInflater.inflate(
  76. R.layout.pull_to_refresh_header, this, false);
  77. mRefreshViewText =
  78. (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_text);
  79. mRefreshViewImage =
  80. (ImageView) mRefreshView.findViewById(R.id.pull_to_refresh_image);
  81. mRefreshViewProgress =
  82. (ProgressBar) mRefreshView.findViewById(R.id.pull_to_refresh_progress);
  83. mRefreshViewLastUpdated =
  84. (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_updated_at);
  85. mRefreshViewImage.setMinimumHeight(50);
  86. mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();
  87. mRefreshState = TAP_TO_REFRESH;
  88. //为listview头部增加一个view
  89. addHeaderView(mRefreshView);
  90. super.setOnScrollListener(this);
  91. measureView(mRefreshView);
  92. mRefreshViewHeight = mRefreshView.getMeasuredHeight();
  93. }
  94. @Override
  95. protected void onAttachedToWindow() {
  96. setSelection(1);
  97. }
  98. @Override
  99. public void setAdapter(ListAdapter adapter) {
  100. super.setAdapter(adapter);
  101. setSelection(1);
  102. }
  103. /**
  104. * 设置滑动监听器
  105. *
  106. */
  107. @Override
  108. public void setOnScrollListener(AbsListView.OnScrollListener l) {
  109. mOnScrollListener = l;
  110. }
  111. /**
  112. * 注册一个list需要刷新时的回调接口
  113. *
  114. */
  115. public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
  116. mOnRefreshListener = onRefreshListener;
  117. }
  118. /**
  119. * 设置标签显示何时最后被刷新
  120. *
  121. * @param lastUpdated
  122. *            Last updated at.
  123. */
  124. public void setLastUpdated(CharSequence lastUpdated) {
  125. if (lastUpdated != null) {
  126. mRefreshViewLastUpdated.setVisibility(View.VISIBLE);
  127. mRefreshViewLastUpdated.setText(lastUpdated);
  128. } else {
  129. mRefreshViewLastUpdated.setVisibility(View.GONE);
  130. }
  131. }
  132. // 实现该方法处理触摸
  133. @Override
  134. public boolean onTouchEvent(MotionEvent event) {
  135. final int y = (int) event.getY();
  136. mBounceHack = false;
  137. switch (event.getAction()) {
  138. case MotionEvent.ACTION_UP:
  139. if (!isVerticalScrollBarEnabled()) {
  140. setVerticalScrollBarEnabled(true);
  141. }
  142. if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {
  143. // 拖动距离达到刷新需要
  144. if ((mRefreshView.getBottom() >= mRefreshViewHeight
  145. || mRefreshView.getTop() >= 0)
  146. && mRefreshState == RELEASE_TO_REFRESH) {
  147. // 把状态设置为正在刷新
  148. mRefreshState = REFRESHING;
  149. // 准备刷新
  150. prepareForRefresh();
  151. // 刷新
  152. onRefresh();
  153. } else if (mRefreshView.getBottom() < mRefreshViewHeight
  154. || mRefreshView.getTop() <= 0) {
  155. // 中止刷新
  156. resetHeader();
  157. setSelection(1);
  158. }
  159. }
  160. break;
  161. case MotionEvent.ACTION_DOWN:
  162. // 获得按下y轴位置
  163. mLastMotionY = y;
  164. break;
  165. case MotionEvent.ACTION_MOVE:
  166. // 计算边距
  167. applyHeaderPadding(event);
  168. break;
  169. }
  170. return super.onTouchEvent(event);
  171. }
  172. // 获得header的边距
  173. private void applyHeaderPadding(MotionEvent ev) {
  174. int pointerCount = ev.getHistorySize();
  175. for (int p = 0; p < pointerCount; p++) {
  176. if (mRefreshState == RELEASE_TO_REFRESH) {
  177. if (isVerticalFadingEdgeEnabled()) {
  178. setVerticalScrollBarEnabled(false);
  179. }
  180. int historicalY = (int) ev.getHistoricalY(p);
  181. // 计算申请的边距,除以1.7使得拉动效果更好
  182. int topPadding = (int) (((historicalY - mLastMotionY)
  183. - mRefreshViewHeight) / 1.7);
  184. mRefreshView.setPadding(
  185. mRefreshView.getPaddingLeft(),
  186. topPadding,
  187. mRefreshView.getPaddingRight(),
  188. mRefreshView.getPaddingBottom());
  189. }
  190. }
  191. }
  192. /**
  193. * 将head的边距重置为初始的数值
  194. */
  195. private void resetHeaderPadding() {
  196. mRefreshView.setPadding(
  197. mRefreshView.getPaddingLeft(),
  198. mRefreshOriginalTopPadding,
  199. mRefreshView.getPaddingRight(),
  200. mRefreshView.getPaddingBottom());
  201. }
  202. /**
  203. * 重置header为之前的状态
  204. */
  205. private void resetHeader() {
  206. if (mRefreshState != TAP_TO_REFRESH) {
  207. mRefreshState = TAP_TO_REFRESH;
  208. resetHeaderPadding();
  209. // 将刷新图标换成箭头
  210. mRefreshViewImage.setImageResource(R.drawable.ic_pulltorefresh_arrow);
  211. // 清除动画
  212. mRefreshViewImage.clearAnimation();
  213. // 隐藏图标和进度条
  214. mRefreshViewImage.setVisibility(View.GONE);
  215. mRefreshViewProgress.setVisibility(View.GONE);
  216. }
  217. }
  218. // 估算headview的width和height
  219. private void measureView(View child) {
  220. ViewGroup.LayoutParams p = child.getLayoutParams();
  221. if (p == null) {
  222. p = new ViewGroup.LayoutParams(
  223. ViewGroup.LayoutParams.FILL_PARENT,
  224. ViewGroup.LayoutParams.WRAP_CONTENT);
  225. }
  226. int childWidthSpec = ViewGroup.getChildMeasureSpec(0,
  227. 0 + 0, p.width);
  228. int lpHeight = p.height;
  229. int childHeightSpec;
  230. if (lpHeight > 0) {
  231. childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
  232. } else {
  233. childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
  234. }
  235. child.measure(childWidthSpec, childHeightSpec);
  236. }
  237. @Override
  238. public void onScroll(AbsListView view, int firstVisibleItem,
  239. int visibleItemCount, int totalItemCount) {
  240. // 在refreshview完全可见时,设置文字为松开刷新,同时翻转箭头
  241. if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
  242. && mRefreshState != REFRESHING) {
  243. if (firstVisibleItem == 0) {
  244. mRefreshViewImage.setVisibility(View.VISIBLE);
  245. if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20
  246. || mRefreshView.getTop() >= 0)
  247. && mRefreshState != RELEASE_TO_REFRESH) {
  248. mRefreshViewText.setText("松开加载...");
  249. mRefreshViewImage.clearAnimation();
  250. mRefreshViewImage.startAnimation(mFlipAnimation);
  251. mRefreshState = RELEASE_TO_REFRESH;
  252. } else if (mRefreshView.getBottom() < mRefreshViewHeight + 20
  253. && mRefreshState != PULL_TO_REFRESH) {
  254. mRefreshViewText.setText("下拉刷新...");
  255. if (mRefreshState != TAP_TO_REFRESH) {
  256. mRefreshViewImage.clearAnimation();
  257. mRefreshViewImage.startAnimation(mReverseFlipAnimation);
  258. }
  259. mRefreshState = PULL_TO_REFRESH;
  260. }
  261. } else {
  262. mRefreshViewImage.setVisibility(View.GONE);
  263. resetHeader();
  264. }
  265. } else if (mCurrentScrollState == SCROLL_STATE_FLING
  266. && firstVisibleItem == 0
  267. && mRefreshState != REFRESHING) {
  268. setSelection(1);
  269. mBounceHack = true;
  270. } else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {
  271. setSelection(1);
  272. }
  273. if (mOnScrollListener != null) {
  274. mOnScrollListener.onScroll(view, firstVisibleItem,
  275. visibleItemCount, totalItemCount);
  276. }
  277. }
  278. @Override
  279. public void onScrollStateChanged(AbsListView view, int scrollState) {
  280. mCurrentScrollState = scrollState;
  281. if (mCurrentScrollState == SCROLL_STATE_IDLE) {
  282. mBounceHack = false;
  283. }
  284. if (mOnScrollListener != null) {
  285. mOnScrollListener.onScrollStateChanged(view, scrollState);
  286. }
  287. }
  288. public void prepareForRefresh() {
  289. resetHeaderPadding();// 恢复header的边距
  290. mRefreshViewImage.setVisibility(View.GONE);
  291. // 注意加上,否则仍然显示之前的图片
  292. mRefreshViewImage.setImageDrawable(null);
  293. mRefreshViewProgress.setVisibility(View.VISIBLE);
  294. // 设置文字
  295. mRefreshViewText.setText("加载中...");
  296. mRefreshState = REFRESHING;
  297. }
  298. public void onRefresh() {
  299. if (mOnRefreshListener != null) {
  300. mOnRefreshListener.onRefresh();
  301. }
  302. }
  303. /**
  304. * 重置listview为普通的listview,该方法设置最后更新时间
  305. *
  306. * @param lastUpdated
  307. *            Last updated at.
  308. */
  309. public void onRefreshComplete(CharSequence lastUpdated) {
  310. setLastUpdated(lastUpdated);
  311. onRefreshComplete();
  312. }
  313. /**
  314. * 重置listview为普通的listview,不设置最后更新时间
  315. */
  316. public void onRefreshComplete() {
  317. resetHeader();
  318. // 如果refreshview在加载结束后可见,下滑到下一个条目
  319. if (mRefreshView.getBottom() > 0) {
  320. invalidateViews();
  321. setSelection(1);
  322. }
  323. }
  324. /**
  325. * 刷新监听器接口
  326. */
  327. public interface OnRefreshListener {
  328. /**
  329. * list需要被刷新时调用
  330. */
  331. public void onRefresh();
  332. }
  333. }

相信我注释已经写的比较详细了,主要注意
onTouchEvent和onScroll方法,在这里面计算头部边距,从而通过用户的手势实现“下拉刷新”到“松开加载”以及“加载”三个状态的切
换。其中还有一系列和header有关的方法,用来设置header的显示以及取得header的边距。于此同时,代码留出了接口以供调用。

那么现在写一个测试Activity来试验下效果:

  1. package com.notice.pullrefresh;
  2. import java.util.Arrays;
  3. import java.util.LinkedList;
  4. import android.app.ListActivity;
  5. import android.os.AsyncTask;
  6. import android.os.Bundle;
  7. import android.widget.ArrayAdapter;
  8. import com.notice.pullrefresh.PullToRefreshListView.OnRefreshListener;
  9. public class PullrefreshActivity extends ListActivity {
  10. private LinkedList<String> mListItems;
  11. ArrayAdapter<String> adapter;
  12. /** Called when the activity is first created. */
  13. @Override
  14. public void onCreate(Bundle savedInstanceState) {
  15. super.onCreate(savedInstanceState);
  16. setContentView(R.layout.pull_to_refresh);
  17. // list需要刷新时调用
  18. ((PullToRefreshListView) getListView())
  19. .setOnRefreshListener(new OnRefreshListener() {
  20. @Override
  21. public void onRefresh() {
  22. // 在这执行后台工作
  23. new GetDataTask().execute();
  24. }
  25. });
  26. mListItems = new LinkedList<String>();
  27. mListItems.addAll(Arrays.asList(mStrings));
  28. adapter = new ArrayAdapter<String>(this,
  29. android.R.layout.simple_list_item_1, mListItems);
  30. setListAdapter(adapter);
  31. }
  32. private class GetDataTask extends AsyncTask<Void, Void, String[]> {
  33. @Override
  34. protected String[] doInBackground(Void... params) {
  35. // 在这里可以做一些后台工作
  36. try {
  37. Thread.sleep(2000);
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41. return mStrings;
  42. }
  43. @Override
  44. protected void onPostExecute(String[] result) {
  45. // 下拉后增加的内容
  46. mListItems.addFirst("Added after refresh...");
  47. // 刷新完成调用该方法复位
  48. ((PullToRefreshListView) getListView()).onRefreshComplete();
  49. super.onPostExecute(result);
  50. }
  51. }
  52. private String[] mStrings = { "normal data1", "normal data2",
  53. "nomal data3", "normal data4", "norma data5", "normal data6" };
  54. }

代码通过asyncTask实现一个异步操作,并通过设置onRefreshListener监听器调用onRefresh方法实现下拉时刷新,并在刷新完成后调用onRefreshComplete做复位处理。

android UI进阶之实现listview的下拉加载的更多相关文章

  1. Android中自定义ListView实现上拉加载更多和下拉刷新

    ListView是Android中一个功能强大而且很常用的控件,在很多App中都有ListView的下拉刷新数据和上拉加载更多这个功能.这里我就简单记录一下实现过程. 实现这个功能的方法不止一个,Gi ...

  2. WP & Win10开发:实现ListView下拉加载的两种方法

    1.通过ListView控件的ContainerContentChanging方法.该方法在列表项被实例化时触发,在列表项最后一个项目实例化的时候触发刷新数据逻辑就可以实现下拉加载了. 代码如下:// ...

  3. Android之 RecyclerView,CardView 详解和相对应的上拉刷新下拉加载

    随着 Google 推出了全新的设计语言 Material Design,还迎来了新的 Android 支持库 v7,其中就包含了 Material Design 设计语言中关于 Card 卡片概念的 ...

  4. Flutter学习笔记(25)--ListView实现上拉刷新下拉加载

    如需转载,请注明出处:Flutter学习笔记(25)--ListView实现上拉刷新下拉加载 前面我们有写过ListView的使用:Flutter学习笔记(12)--列表组件,当列表的数据非常多时,需 ...

  5. Windows Phone 8.1开发:如何让ListView下拉加载更多?

    Windows Phone 8.1开发中使用ListView作为数据呈现载体时,经常需要一个下拉(拇指向上滑动)加载更多的交互操作.如何完成这一操作呢?下面为您阐述. 思路是这样的: 1.在ListV ...

  6. Flutter 开发从 0 到 1(四)ListView 下拉加载和加载更多

    在<APP 开发从 0 到 1(三)布局与 ListView>我们完成了 ListView,这篇文章将做 ListView 下拉加载和加载更多. ListView 下拉加载 Flutter ...

  7. 使用谷歌提供的SwipeRefreshLayout下拉控件,并自定义实现下拉加载的功能

    package com.loaderman.swiperefreshdemo; import android.os.Bundle; import android.os.Handler; import ...

  8. 美团、点评、猫眼App下拉加载效果的源码分享

    今天我准备拿大众点评.美团.猫眼电影三款App的实例来分享一下APICloud下拉加载这个模块的效果. 美团App下拉加载效果   以美团中的下拉酷似动画的萌萌着小人儿效果作为参考,来实现的一个加载模 ...

  9. PullToRefreshGridView上拉刷新,下拉加载

    PullToRefreshGridView上拉刷新,下拉加载 布局: <?xml version="1.0" encoding="utf-8"?> ...

随机推荐

  1. python的Requests模块使用tips

    post方法提交的是表单,要用data放dict get方法请求的是参数,要用params放dict HTTP头部是大小写不敏感的

  2. web自动化框架之一介绍与环境搭建(Selenium+Eclipse+Python)

    看到一篇环境搭建文章,详细又全面,这里就不一一重复了 http://blog.csdn.net/dyllove98/article/details/9390649 其它: 1.框架介绍      整个 ...

  3. iBeacon开发

    什么是iBeacons iBeacons是苹果在2013年WWDC上推出一项基于蓝牙4.0(Bluetooth LE | BLE | Bluetooth Smart)的精准微定位技术,当你的手持设备靠 ...

  4. [Hive - LanguageManual] GroupBy

    Group By Syntax Simple Examples Select statement and group by clause Advanced Features Multi-Group-B ...

  5. vim对erlang语法支持

    发现vim写erlang代码语法缩进都不对,后来发现vim是7.0的,vim7.3开始才对erlang这块进行了支持,所以升级vim git上下载源码包,然后一系列配置安装 http://www.2c ...

  6. Hadoop 2.2 YARN分布式集群搭建配置流程

    搭建环境准备:JDK1.6,SSH免密码通信 系统:CentOS 6.3 集群配置:NameNode和ResourceManager在一台服务器上,三个数据节点 搭建用户:YARN Hadoop2.2 ...

  7. HttpComponents 学习的两个重要文档

    httpcore-tutorial-simplified-chinese httpclient-tutorial-simplified-chinese

  8. HDU ACM Eight

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1043 解题背景: 看到八数码问题,没有任何的想法,偶然在翻看以前做的题的时候发现解决过类似的一道题,不 ...

  9. sass学习(1)——了解sass

    为什么要选择sass 我们在手写css中,会遇到很多很麻烦的问题.倒不是一些技术的问题,而是工程量的问题.例如,如何可以代替难记的16进制颜色,如何可以让层次更清晰,还有重复的代码该如何偷懒.其实这一 ...

  10. Cygwin的包管理器:apt-cyg

    参考<Cygwin的包管理器:apt-cyg> cygwin下安装每次需要启动set_up,比较蛋疼,还是debian的apt方便,在网上看到应该cygwin 下的apt,觉得不错. 从h ...