android ListView的上部下拉刷新下部点击加载更多具体实现及拓展

转自:http://blog.csdn.net/jj120522/article/details/8229423

这次就不上图了,例子太多太多了,想必大家都见过.这个功能的实现,简直是开发者必备的.

我也不过多介绍了,网上详细介绍的博客太多太多了,若想深入了解,请参考网上其他博文.

在这里,我只是按照自己的理解,模拟实现了一个,顺便代码贡献出来.

我对之详细标明的注释,想必如果不懂的同学们,看注释也应该明白,前提是,你要耐心看,因为代码有点多,但是我整理过了,还算清晰.

详细代码:

[java] view plaincopy

  1. package com.jj.drag;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.LayoutInflater;
  6. import android.view.MotionEvent;
  7. import android.view.View;
  8. import android.view.View.OnClickListener;
  9. import android.view.ViewGroup;
  10. import android.view.animation.Animation;
  11. import android.view.animation.LinearInterpolator;
  12. import android.view.animation.RotateAnimation;
  13. import android.widget.AbsListView;
  14. import android.widget.AbsListView.OnScrollListener;
  15. import android.widget.ImageView;
  16. import android.widget.LinearLayout;
  17. import android.widget.ListView;
  18. import android.widget.ProgressBar;
  19. import android.widget.RelativeLayout;
  20. import android.widget.TextView;
  21. /***
  22. * 自定义拖拉ListView
  23. *
  24. * @author zhangjia
  25. *
  26. */
  27. public class DragListView extends ListView implements OnScrollListener,
  28. OnClickListener {
  29. // 拖拉ListView枚举所有状态
  30. private enum DListViewState {
  31. LV_NORMAL, // 普通状态
  32. LV_PULL_REFRESH, // 下拉状态(为超过mHeadViewHeight)
  33. LV_RELEASE_REFRESH, // 松开可刷新状态(超过mHeadViewHeight)
  34. LV_LOADING;// 加载状态
  35. }
  36. // 点击加载更多枚举所有状态
  37. private enum DListViewLoadingMore {
  38. LV_NORMAL, // 普通状态
  39. LV_LOADING, // 加载状态
  40. LV_OVER; // 结束状态
  41. }
  42. private View mHeadView;// 头部headView
  43. private TextView mRefreshTextview; // 刷新msg(mHeadView)
  44. private TextView mLastUpdateTextView;// 更新事件(mHeadView)
  45. private ImageView mArrowImageView;// 下拉图标(mHeadView)
  46. private ProgressBar mHeadProgressBar;// 刷新进度体(mHeadView)
  47. private int mHeadViewWidth; // headView的宽(mHeadView)
  48. private int mHeadViewHeight;// headView的高(mHeadView)
  49. private View mFootView;// 尾部mFootView
  50. private View mLoadMoreView;// mFootView 的view(mFootView)
  51. private TextView mLoadMoreTextView;// 加载更多.(mFootView)
  52. private View mLoadingView;// 加载中...View(mFootView)
  53. private Animation animation, reverseAnimation;// 旋转动画,旋转动画之后旋转动画.
  54. private int mFirstItemIndex = -1;// 当前视图能看到的第一个项的索引
  55. // 用于保证startY的值在一个完整的touch事件中只被记录一次
  56. private boolean mIsRecord = false;
  57. private int mStartY, mMoveY;// 按下是的y坐标,move时的y坐标
  58. private DListViewState mlistViewState = DListViewState.LV_NORMAL;// 拖拉状态.(自定义枚举)
  59. private DListViewLoadingMore loadingMoreState = DListViewLoadingMore.LV_NORMAL;// 加载更多默认状态.
  60. private final static int RATIO = 2;// 手势下拉距离比.
  61. private boolean mBack = false;// headView是否返回.
  62. private OnRefreshLoadingMoreListener onRefreshLoadingMoreListener;// 下拉刷新接口(自定义)
  63. private boolean isScroller = true;// 是否屏蔽ListView滑动。
  64. public DragListView(Context context) {
  65. super(context, null);
  66. initDragListView(context);
  67. }
  68. public DragListView(Context context, AttributeSet attrs) {
  69. super(context, attrs);
  70. initDragListView(context);
  71. }
  72. // 注入下拉刷新接口
  73. public void setOnRefreshListener(
  74. OnRefreshLoadingMoreListener onRefreshLoadingMoreListener) {
  75. this.onRefreshLoadingMoreListener = onRefreshLoadingMoreListener;
  76. }
  77. /***
  78. * 初始化ListView
  79. */
  80. public void initDragListView(Context context) {
  81. String time = "1994.12.05";// 更新时间
  82. initHeadView(context, time);// 初始化该head.
  83. initLoadMoreView(context);// 初始化footer
  84. setOnScrollListener(this);// ListView滚动监听
  85. }
  86. /***
  87. * 初始话头部HeadView
  88. *
  89. * @param context
  90. *            上下文
  91. * @param time
  92. *            上次更新时间
  93. */
  94. public void initHeadView(Context context, String time) {
  95. mHeadView = LayoutInflater.from(context).inflate(R.layout.head, null);
  96. mArrowImageView = (ImageView) mHeadView
  97. .findViewById(R.id.head_arrowImageView);
  98. mArrowImageView.setMinimumWidth(60);
  99. mHeadProgressBar = (ProgressBar) mHeadView
  100. .findViewById(R.id.head_progressBar);
  101. mRefreshTextview = (TextView) mHeadView
  102. .findViewById(R.id.head_tipsTextView);
  103. mLastUpdateTextView = (TextView) mHeadView
  104. .findViewById(R.id.head_lastUpdatedTextView);
  105. // 显示更新事件
  106. mLastUpdateTextView.setText("最近更新:" + time);
  107. measureView(mHeadView);
  108. // 获取宽和高
  109. mHeadViewWidth = mHeadView.getMeasuredWidth();
  110. mHeadViewHeight = mHeadView.getMeasuredHeight();
  111. addHeaderView(mHeadView, null, false);// 将初始好的ListView add进拖拽ListView
  112. // 在这里我们要将此headView设置到顶部不显示位置.
  113. mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);
  114. initAnimation();// 初始化动画
  115. }
  116. /***
  117. * 初始化底部加载更多控件
  118. */
  119. private void initLoadMoreView(Context context) {
  120. mFootView = LayoutInflater.from(context).inflate(R.layout.footer, null);
  121. mLoadMoreView = mFootView.findViewById(R.id.load_more_view);
  122. mLoadMoreTextView = (TextView) mFootView
  123. .findViewById(R.id.load_more_tv);
  124. mLoadingView = (LinearLayout) mFootView
  125. .findViewById(R.id.loading_layout);
  126. mLoadMoreView.setOnClickListener(this);
  127. addFooterView(mFootView);
  128. }
  129. /***
  130. * 初始化动画
  131. */
  132. private void initAnimation() {
  133. // 旋转动画
  134. animation = new RotateAnimation(0, -180,
  135. RotateAnimation.RELATIVE_TO_SELF, 0.5f,
  136. RotateAnimation.RELATIVE_TO_SELF, 0.5f);
  137. animation.setInterpolator(new LinearInterpolator());// 匀速
  138. animation.setDuration(250);
  139. animation.setFillAfter(true);// 停留在最后状态.
  140. // 反向旋转动画
  141. reverseAnimation = new RotateAnimation(-180, 0,
  142. RotateAnimation.RELATIVE_TO_SELF, 0.5f,
  143. RotateAnimation.RELATIVE_TO_SELF, 0.5f);
  144. reverseAnimation.setInterpolator(new LinearInterpolator());
  145. reverseAnimation.setDuration(250);
  146. reverseAnimation.setFillAfter(true);
  147. }
  148. /***
  149. * 作用:测量 headView的宽和高.
  150. *
  151. * @param child
  152. */
  153. private void measureView(View child) {
  154. ViewGroup.LayoutParams p = child.getLayoutParams();
  155. if (p == null) {
  156. p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
  157. ViewGroup.LayoutParams.WRAP_CONTENT);
  158. }
  159. int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
  160. int lpHeight = p.height;
  161. int childHeightSpec;
  162. if (lpHeight > 0) {
  163. childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
  164. MeasureSpec.EXACTLY);
  165. } else {
  166. childHeightSpec = MeasureSpec.makeMeasureSpec(0,
  167. MeasureSpec.UNSPECIFIED);
  168. }
  169. child.measure(childWidthSpec, childHeightSpec);
  170. }
  171. /***
  172. * touch 事件监听
  173. */
  174. @Override
  175. public boolean onTouchEvent(MotionEvent ev) {
  176. switch (ev.getAction()) {
  177. // 按下
  178. case MotionEvent.ACTION_DOWN:
  179. doActionDown(ev);
  180. break;
  181. // 移动
  182. case MotionEvent.ACTION_MOVE:
  183. doActionMove(ev);
  184. break;
  185. // 抬起
  186. case MotionEvent.ACTION_UP:
  187. doActionUp(ev);
  188. break;
  189. default:
  190. break;
  191. }
  192. /***
  193. * 如果是ListView本身的拉动,那么返回true,这样ListView不可以拖动.
  194. * 如果不是ListView的拉动,那么调用父类方法,这样就可以上拉执行.
  195. */
  196. if (isScroller) {
  197. return super.onTouchEvent(ev);
  198. } else {
  199. return true;
  200. }
  201. }
  202. /***
  203. * 摁下操作
  204. *
  205. * 作用:获取摁下是的y坐标
  206. *
  207. * @param event
  208. */
  209. void doActionDown(MotionEvent event) {
  210. if (mIsRecord == false && mFirstItemIndex == 0) {
  211. mStartY = (int) event.getY();
  212. mIsRecord = true;
  213. }
  214. }
  215. /***
  216. * 拖拽移动操作
  217. *
  218. * @param event
  219. */
  220. void doActionMove(MotionEvent event) {
  221. mMoveY = (int) event.getY();// 获取实时滑动y坐标
  222. // 检测是否是一次touch事件.
  223. if (mIsRecord == false && mFirstItemIndex == 0) {
  224. mStartY = (int) event.getY();
  225. mIsRecord = true;
  226. }
  227. /***
  228. * 如果touch关闭或者正处于Loading状态的话 return.
  229. */
  230. if (mIsRecord == false || mlistViewState == DListViewState.LV_LOADING) {
  231. return;
  232. }
  233. // 向下啦headview移动距离为y移动的一半.(比较友好)
  234. int offset = (mMoveY - mStartY) / RATIO;
  235. switch (mlistViewState) {
  236. // 普通状态
  237. case LV_NORMAL: {
  238. // 如果<0,则意味着上滑动.
  239. if (offset > 0) {
  240. // 设置headView的padding属性.
  241. mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);
  242. switchViewState(DListViewState.LV_PULL_REFRESH);// 下拉状态
  243. }
  244. }
  245. break;
  246. // 下拉状态
  247. case LV_PULL_REFRESH: {
  248. setSelection(0);// 时时保持在顶部.
  249. // 设置headView的padding属性.
  250. mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);
  251. if (offset < 0) {
  252. /***
  253. * 要明白为什么isScroller = false;
  254. */
  255. isScroller = false;
  256. switchViewState(DListViewState.LV_NORMAL);// 普通状态
  257. Log.e("jj", "isScroller=" + isScroller);
  258. } else if (offset > mHeadViewHeight) {// 如果下拉的offset超过headView的高度则要执行刷新.
  259. switchViewState(DListViewState.LV_RELEASE_REFRESH);// 更新为可刷新的下拉状态.
  260. }
  261. }
  262. break;
  263. // 可刷新状态
  264. case LV_RELEASE_REFRESH: {
  265. setSelection(0);时时保持在顶部
  266. // 设置headView的padding属性.
  267. mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);
  268. // 下拉offset>0,但是没有超过headView的高度.那么要goback 原装.
  269. if (offset >= 0 && offset <= mHeadViewHeight) {
  270. mBack = true;
  271. switchViewState(DListViewState.LV_PULL_REFRESH);
  272. } else if (offset < 0) {
  273. switchViewState(DListViewState.LV_NORMAL);
  274. } else {
  275. }
  276. }
  277. break;
  278. default:
  279. return;
  280. }
  281. ;
  282. }
  283. /***
  284. * 手势抬起操作
  285. *
  286. * @param event
  287. */
  288. public void doActionUp(MotionEvent event) {
  289. mIsRecord = false;// 此时的touch事件完毕,要关闭。
  290. isScroller = true;// ListView可以Scrooler滑动.
  291. mBack = false;
  292. // 如果下拉状态处于loading状态.
  293. if (mlistViewState == DListViewState.LV_LOADING) {
  294. return;
  295. }
  296. // 处理相应状态.
  297. switch (mlistViewState) {
  298. // 普通状态
  299. case LV_NORMAL:
  300. break;
  301. // 下拉状态
  302. case LV_PULL_REFRESH:
  303. mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);
  304. switchViewState(mlistViewState.LV_NORMAL);
  305. break;
  306. // 刷新状态
  307. case LV_RELEASE_REFRESH:
  308. mHeadView.setPadding(0, 0, 0, 0);
  309. switchViewState(mlistViewState.LV_LOADING);
  310. onRefresh();// 下拉刷新
  311. break;
  312. }
  313. }
  314. // 切换headview视图
  315. private void switchViewState(DListViewState state) {
  316. switch (state) {
  317. // 普通状态
  318. case LV_NORMAL: {
  319. mArrowImageView.clearAnimation();// 清除动画
  320. mArrowImageView.setImageResource(R.drawable.arrow);
  321. }
  322. break;
  323. // 下拉状态
  324. case LV_PULL_REFRESH: {
  325. mHeadProgressBar.setVisibility(View.GONE);// 隐藏进度条
  326. mArrowImageView.setVisibility(View.VISIBLE);// 下拉图标
  327. mRefreshTextview.setText("下拉可以刷新");
  328. mArrowImageView.clearAnimation();// 清除动画
  329. // 是有可刷新状态(LV_RELEASE_REFRESH)转为这个状态才执行,其实就是你下拉后在上拉会执行.
  330. if (mBack) {
  331. mBack = false;
  332. mArrowImageView.clearAnimation();// 清除动画
  333. mArrowImageView.startAnimation(reverseAnimation);// 启动反转动画
  334. }
  335. }
  336. break;
  337. // 松开刷新状态
  338. case LV_RELEASE_REFRESH: {
  339. mHeadProgressBar.setVisibility(View.GONE);// 隐藏进度条
  340. mArrowImageView.setVisibility(View.VISIBLE);// 显示下拉图标
  341. mRefreshTextview.setText("松开获取更多");
  342. mArrowImageView.clearAnimation();// 清除动画
  343. mArrowImageView.startAnimation(animation);// 启动动画
  344. }
  345. break;
  346. // 加载状态
  347. case LV_LOADING: {
  348. Log.e("!!!!!!!!!!!", "convert to IListViewState.LVS_LOADING");
  349. mHeadProgressBar.setVisibility(View.VISIBLE);
  350. mArrowImageView.clearAnimation();
  351. mArrowImageView.setVisibility(View.GONE);
  352. mRefreshTextview.setText("载入中...");
  353. }
  354. break;
  355. default:
  356. return;
  357. }
  358. // 切记不要忘记时时更新状态。
  359. mlistViewState = state;
  360. }
  361. /***
  362. * 下拉刷新
  363. */
  364. private void onRefresh() {
  365. if (onRefreshLoadingMoreListener != null) {
  366. onRefreshLoadingMoreListener.onRefresh();
  367. }
  368. }
  369. /***
  370. * 下拉刷新完毕
  371. */
  372. public void onRefreshComplete() {
  373. mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);// 回归.
  374. switchViewState(mlistViewState.LV_NORMAL);//
  375. }
  376. /***
  377. * 点击加载更多
  378. *
  379. * @param flag
  380. *            数据是否已全部加载完毕
  381. */
  382. public void onLoadMoreComplete(boolean flag) {
  383. if (flag) {
  384. updateLoadMoreViewState(DListViewLoadingMore.LV_OVER);
  385. } else {
  386. updateLoadMoreViewState(DListViewLoadingMore.LV_NORMAL);
  387. }
  388. }
  389. // 更新Footview视图
  390. private void updateLoadMoreViewState(DListViewLoadingMore state) {
  391. switch (state) {
  392. // 普通状态
  393. case LV_NORMAL:
  394. mLoadingView.setVisibility(View.GONE);
  395. mLoadMoreTextView.setVisibility(View.VISIBLE);
  396. mLoadMoreTextView.setText("查看更多");
  397. break;
  398. // 加载中状态
  399. case LV_LOADING:
  400. mLoadingView.setVisibility(View.VISIBLE);
  401. mLoadMoreTextView.setVisibility(View.GONE);
  402. break;
  403. // 加载完毕状态
  404. case LV_OVER:
  405. mLoadingView.setVisibility(View.GONE);
  406. mLoadMoreTextView.setVisibility(View.VISIBLE);
  407. mLoadMoreTextView.setText("加载完毕");
  408. break;
  409. default:
  410. break;
  411. }
  412. loadingMoreState = state;
  413. }
  414. /***
  415. * ListView 滑动监听
  416. */
  417. @Override
  418. public void onScrollStateChanged(AbsListView view, int scrollState) {
  419. }
  420. @Override
  421. public void onScroll(AbsListView view, int firstVisibleItem,
  422. int visibleItemCount, int totalItemCount) {
  423. mFirstItemIndex = firstVisibleItem;
  424. }
  425. /***
  426. * 底部点击事件
  427. */
  428. @Override
  429. public void onClick(View v) {
  430. // 防止重复点击
  431. if (onRefreshLoadingMoreListener != null
  432. && loadingMoreState == DListViewLoadingMore.LV_NORMAL) {
  433. updateLoadMoreViewState(DListViewLoadingMore.LV_LOADING);
  434. onRefreshLoadingMoreListener.onLoadMore();// 对外提供方法加载更多.
  435. }
  436. }
  437. /***
  438. * 自定义接口
  439. */
  440. public interface OnRefreshLoadingMoreListener {
  441. /***
  442. * // 下拉刷新执行
  443. */
  444. void onRefresh();
  445. /***
  446. * 点击加载更多
  447. */
  448. void onLoadMore();
  449. }
  450. }

上面就是全部代码,其实重要的是明白理解,这样我们还可以进行拓展.

具体应用:(只需要这样引用即可.)

[java] view plaincopy

  1. <com.jj.drag.DragListView
  2. android:id="@+id/dlv_main"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:cacheColorHint="#00000000" />

在Activity中的调用,相比大家都清楚,开个异步或线程进行加载数据,这里我简单说一下异步使用,线程同理.

代码如下:

[java] view plaincopy

  1. /***
  2. * 执行类 异步
  3. *
  4. * @author zhangjia
  5. *
  6. */
  7. class MyAsyncTask extends AsyncTask<Void, Void, Void> {
  8. private Context context;
  9. private int index;// 用于判断是下拉刷新还是点击加载更多
  10. public MyAsyncTask(Context context, int index) {
  11. this.context = context;
  12. this.index = index;
  13. }
  14. @Override
  15. protected Void doInBackground(Void... params) {
  16. try {
  17. Thread.sleep(2000);
  18. } catch (InterruptedException e1) {
  19. e1.printStackTrace();
  20. }
  21. return null;
  22. }
  23. @Override
  24. protected void onPreExecute() {
  25. super.onPreExecute();
  26. }
  27. @Override
  28. protected void onPostExecute(Void result) {
  29. super.onPostExecute(result);
  30. if (index == DRAG_INDEX)
  31. dlv_main.onRefreshComplete();
  32. else if (index == LOADMORE_INDEX)
  33. dlv_main.onLoadMoreComplete(false);
  34. }
  35. }

先声明一点,这个只是个示例,所以这部分代码写的不够友好,也请见谅.

就说道这里,最后展示一下效果:

                

至于如果显示,如何adapter.notifyDataSetChanged();那就要大家开发时候自己调理了.

最后说明一点:网上有好多介绍下拉刷新的例子,但是他们没有对滑动进行处理,比如,我下拉的时候现在不想刷新了,这时我又向上滑动,正常的处理,应该滑动到FirstItemIndex=1就是顶部,滑动就结束了.(意思就是要下拉和listview正常滑动要分开)可是网上一些案例都没有对之处理,用起来不友好,大家可以看看成功案例,那些新浪,腾讯,百度等.

解决方法:(这是onTouch方法中的一部分.)

[java] view plaincopy

  1. /***
  2. * 如果是ListView本身的拉动,那么返回true,这样ListView不可以拖动.
  3. * 如果不是ListView的拉动,那么调用父类方法,这样就可以上拉执行.
  4. */
  5. if (isScroller) {
  6. return super.onTouchEvent(ev);
  7. } else {
  8. return true;
  9. }

要问Why的话,那么你就要去详细看Touch种种事件,记住,这里用到的不是分发与拦截,分发拦截流程如下:

Activity 的dispatchTouchEvent开始分发给子的View,如果该View是ViewGroup的话,那么执行其dispatchTouchEvent进行分发,在执行相应的onInterceptTouchEvent拦截.如果要想实现上诉说的那种效果,那么在自定义ListView中对拦截分发方法是无效的,只有在ListView的上一层进行处理,比我我们在外层自定义一个布局,等等,实现起来总之麻烦一个字,其实我们也可以考虑考虑onTouchEvent事件的实现,

ListView.java

[java] view plaincopy

  1. @Override
  2. public boolean onTouchEvent(MotionEvent ev) {
  3. if (mItemsCanFocus && ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
  4. // Don‘t handle edge touches immediately -- they may actually belong to one of our
  5. // descendants.
  6. return false;
  7. }
  8. return super.onTouchEvent(ev);
  9. }

继续点击查看父类,这里就不显示了,自己可以查看源码,其实就是我们ListView滑动的具体实现,而此时我们只是想临时屏蔽掉此滑动,那么我们只需要不调用父类的onTouchEvent不就OK的,是的,确实如此,而何时进行屏蔽,大家就仔细看上面源码实现吧,解释的也很清楚,这样大家都明白了吧。注:有疑问请留言!之前这个例子android 自定义ScrollView实现反弹效果(以及解决和ListView之间的冲突)没有解决这个问题,因为处境不同.(不过正在完善,相信也会完美的实现这些效果,因为原理上是行的通的。)

知识拓展:

首先我们还是看一些案例:

      

效果就是可以上下拖拽.而用在最多的地方就是ListView,而普通的布局拖拽直接自定义布局就OK了,详情请参考上面连接那篇文章.

实现起来也不是很麻烦,就是对上面那个自定义类稍作修改,把底部也做成动态拖拽效果就OK了.

这里不详细讲解,因为注释相当明确,如有疑问,请指出.

代码如下:

[java] view plaincopy

  1. package com.jj.drag;
  2. import android.content.Context;
  3. import android.os.AsyncTask;
  4. import android.util.AttributeSet;
  5. import android.util.Log;
  6. import android.view.LayoutInflater;
  7. import android.view.MotionEvent;
  8. import android.view.View;
  9. import android.view.View.OnClickListener;
  10. import android.view.ViewGroup;
  11. import android.view.animation.Animation;
  12. import android.view.animation.LinearInterpolator;
  13. import android.view.animation.RotateAnimation;
  14. import android.widget.AbsListView;
  15. import android.widget.AbsListView.OnScrollListener;
  16. import android.widget.ImageView;
  17. import android.widget.LinearLayout;
  18. import android.widget.ListView;
  19. import android.widget.ProgressBar;
  20. import android.widget.RelativeLayout;
  21. import android.widget.TextView;
  22. /***
  23. * 自定义拖拉ListView
  24. *
  25. * @author zhangjia
  26. *
  27. */
  28. public class DragListView extends ListView implements OnScrollListener,
  29. OnClickListener {
  30. // 下拉ListView枚举所有状态
  31. private enum DListViewState {
  32. LV_NORMAL, // 普通状态
  33. LV_PULL_REFRESH, // 下拉状态(为超过mHeadViewHeight)
  34. }
  35. // 点击加载更多枚举所有状态
  36. private enum DListViewLoadingMore {
  37. LV_NORMAL, // 普通状态
  38. LV_PULL_REFRESH, // 上拉状态(为超过mHeadViewHeight)
  39. }
  40. private View mHeadView, mFootView;// 头部headView
  41. private int mHeadViewWidth; // headView的宽(mHeadView)
  42. private int mHeadViewHeight;// headView的高(mHeadView)
  43. private int mFirstItemIndex = -1;// 当前视图能看到的第一个项的索引
  44. private int mLastItemIndex = -1;// 当前视图中是否是最后一项.
  45. // 用于保证startY的值在一个完整的touch事件中只被记录一次
  46. private boolean mIsRecord = false;// 针对下拉
  47. private boolean mIsRecord_B = false;// 针对上拉
  48. private int mStartY, mMoveY;// 按下是的y坐标,move时的y坐标
  49. private DListViewState mlistViewState = DListViewState.LV_NORMAL;// 拖拉状态.(自定义枚举)
  50. private DListViewLoadingMore loadingMoreState = DListViewLoadingMore.LV_NORMAL;// 加载更多默认状态.
  51. private final static int RATIO = 2;// 手势下拉距离比.
  52. private boolean isScroller = true;// 是否屏蔽ListView滑动。
  53. private MyAsynTask myAsynTask;// 任务
  54. private final static int DRAG_UP = 1, DRAG_DOWN = 2;
  55. public DragListView(Context context) {
  56. super(context, null);
  57. initDragListView(context);
  58. }
  59. public DragListView(Context context, AttributeSet attrs) {
  60. super(context, attrs);
  61. initDragListView(context);
  62. }
  63. /***
  64. * 初始化ListView
  65. */
  66. public void initDragListView(Context context) {
  67. initHeadView(context);// 初始化该head.
  68. initFooterView(context);// 初始化footer
  69. setOnScrollListener(this);// ListView滚动监听
  70. }
  71. /***
  72. * 初始话头部HeadView
  73. *
  74. * @param context
  75. *            上下文
  76. * @param time
  77. *            上次更新时间
  78. */
  79. public void initHeadView(Context context) {
  80. mHeadView = LayoutInflater.from(context).inflate(R.layout.head, null);
  81. measureView(mHeadView);
  82. // 获取宽和高
  83. mHeadViewWidth = mHeadView.getMeasuredWidth();
  84. mHeadViewHeight = mHeadView.getMeasuredHeight();
  85. addHeaderView(mHeadView, null, false);// 将初始好的ListView add进拖拽ListView
  86. // 在这里我们要将此headView设置到顶部不显示位置.
  87. mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);
  88. }
  89. /***
  90. * 初始化底部加载更多控件
  91. */
  92. private void initFooterView(Context context) {
  93. mFootView = LayoutInflater.from(context).inflate(R.layout.head, null);
  94. addFooterView(mFootView, null, false);// 将初始好的ListView add进拖拽ListView
  95. // 在这里我们要将此FooterView设置到底部不显示位置.
  96. mFootView.setPadding(0, -1 * mHeadViewHeight, 0, 0);
  97. }
  98. /***
  99. * 作用:测量 headView的宽和高.
  100. *
  101. * @param child
  102. */
  103. private void measureView(View child) {
  104. ViewGroup.LayoutParams p = child.getLayoutParams();
  105. if (p == null) {
  106. p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
  107. ViewGroup.LayoutParams.WRAP_CONTENT);
  108. }
  109. int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
  110. int lpHeight = p.height;
  111. int childHeightSpec;
  112. if (lpHeight > 0) {
  113. childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
  114. MeasureSpec.EXACTLY);
  115. } else {
  116. childHeightSpec = MeasureSpec.makeMeasureSpec(0,
  117. MeasureSpec.UNSPECIFIED);
  118. }
  119. child.measure(childWidthSpec, childHeightSpec);
  120. }
  121. /***
  122. * touch 事件监听
  123. */
  124. @Override
  125. public boolean onTouchEvent(MotionEvent ev) {
  126. switch (ev.getAction()) {
  127. // 按下
  128. case MotionEvent.ACTION_DOWN:
  129. doActionDown_B(ev);
  130. doActionDown(ev);
  131. break;
  132. // 移动
  133. case MotionEvent.ACTION_MOVE:
  134. doActionMove_B(ev);
  135. doActionMove(ev);
  136. break;
  137. // 抬起
  138. case MotionEvent.ACTION_UP:
  139. doActionUp_B(ev);
  140. doActionUp(ev);
  141. break;
  142. default:
  143. break;
  144. }
  145. /***
  146. * 如果是ListView本身的拉动,那么返回true,这样ListView不可以拖动.
  147. * 如果不是ListView的拉动,那么调用父类方法,这样就可以上拉执行.
  148. */
  149. if (isScroller) {
  150. return super.onTouchEvent(ev);
  151. } else {
  152. return true;
  153. }
  154. }
  155. /***
  156. * 摁下操作
  157. *
  158. * 作用:获取摁下是的y坐标
  159. *
  160. * @param event
  161. */
  162. void doActionDown(MotionEvent event) {
  163. // 如果是第一项且是一次touch
  164. if (mIsRecord == false && mFirstItemIndex == 0) {
  165. mStartY = (int) event.getY();
  166. mIsRecord = true;
  167. }
  168. }
  169. /***
  170. * 摁下操作 底部
  171. *
  172. * 作用:获取摁下是的y坐标
  173. */
  174. void doActionDown_B(MotionEvent event) {
  175. // 如果是第一项且是一次touch
  176. if (mIsRecord_B == false && mLastItemIndex == getCount()) {
  177. mStartY = (int) event.getY();
  178. mIsRecord_B = true;
  179. }
  180. }
  181. /***
  182. * 拖拽移动操作
  183. *
  184. * @param event
  185. */
  186. void doActionMove(MotionEvent event) {
  187. // 判断是否是第一项,若不是直接返回
  188. mMoveY = (int) event.getY();// 获取实时滑动y坐标
  189. // 检测是否是一次touch事件.
  190. if (mIsRecord == false && mFirstItemIndex == 0) {
  191. mStartY = (int) event.getY();
  192. mIsRecord = true;
  193. }
  194. // 直接返回说明不是第一项
  195. if (mIsRecord == false)
  196. return;
  197. // 向下啦headview移动距离为y移动的一半.(比较友好)
  198. int offset = (mMoveY - mStartY) / RATIO;
  199. switch (mlistViewState) {
  200. // 普通状态
  201. case LV_NORMAL: {
  202. // 说明下拉
  203. if (offset > 0) {
  204. // 设置headView的padding属性.
  205. mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);
  206. mlistViewState = DListViewState.LV_PULL_REFRESH;// 下拉状态
  207. }
  208. }
  209. break;
  210. // 下拉状态
  211. case LV_PULL_REFRESH: {
  212. setSelection(0);// 时时保持在顶部.
  213. // 设置headView的padding属性.
  214. mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);
  215. if (offset < 0) {
  216. /***
  217. * 要明白为什么isScroller = false;
  218. */
  219. isScroller = false;
  220. mlistViewState = mlistViewState.LV_NORMAL;
  221. }
  222. }
  223. break;
  224. default:
  225. return;
  226. }
  227. }
  228. void doActionMove_B(MotionEvent event) {
  229. mMoveY = (int) event.getY();// 获取实时滑动y坐标
  230. // 检测是否是一次touch事件.(若mFirstItemIndex为0则要初始化mStartY)
  231. if (mIsRecord_B == false && mLastItemIndex == getCount()) {
  232. mStartY = (int) event.getY();
  233. mIsRecord_B = true;
  234. }
  235. // 直接返回说明不是最后一项
  236. if (mIsRecord_B == false)
  237. return;
  238. // 向下啦headview移动距离为y移动的一半.(比较友好)
  239. int offset = (mMoveY - mStartY) / RATIO;
  240. switch (loadingMoreState) {
  241. // 普通状态
  242. case LV_NORMAL: {
  243. // 说明上拉
  244. if (offset < 0) {
  245. int distance = Math.abs(offset);
  246. // 设置headView的padding属性.
  247. mFootView.setPadding(0, distance - mHeadViewHeight, 0, 0);
  248. loadingMoreState = loadingMoreState.LV_PULL_REFRESH;// 下拉状态
  249. }
  250. }
  251. break;
  252. // 上拉状态
  253. case LV_PULL_REFRESH: {
  254. setSelection(getCount() - 1);// 时时保持最底部
  255. // 设置headView的padding属性.
  256. int distance = Math.abs(offset);
  257. mFootView.setPadding(0, distance - mHeadViewHeight, 0, 0);
  258. // 说明下滑
  259. if (offset > 0) {
  260. /***
  261. * 要明白为什么isScroller = false;
  262. */
  263. isScroller = false;
  264. loadingMoreState = loadingMoreState.LV_NORMAL;
  265. }
  266. }
  267. break;
  268. default:
  269. return;
  270. }
  271. }
  272. /***
  273. * 手势抬起操作
  274. *
  275. * @param event
  276. */
  277. public void doActionUp(MotionEvent event) {
  278. mIsRecord = false;// 此时的touch事件完毕,要关闭。
  279. mIsRecord_B = false; // 此时的touch事件完毕,要关闭。
  280. isScroller = true;// ListView可以Scrooler滑动.
  281. mlistViewState = mlistViewState.LV_NORMAL;// 状态也回归最初状态
  282. // 执行相应动画.
  283. myAsynTask = new MyAsynTask();
  284. myAsynTask.execute(DRAG_UP);
  285. }
  286. private void doActionUp_B(MotionEvent event) {
  287. mIsRecord = false;// 此时的touch事件完毕,要关闭。
  288. isScroller = true;// ListView可以Scrooler滑动.
  289. loadingMoreState = loadingMoreState.LV_NORMAL;// 状态也回归最初状态
  290. // 执行相应动画.
  291. myAsynTask = new MyAsynTask();
  292. myAsynTask.execute(DRAG_DOWN);
  293. }
  294. /***
  295. * ListView 滑动监听
  296. */
  297. @Override
  298. public void onScrollStateChanged(AbsListView view, int scrollState) {
  299. }
  300. @Override
  301. public void onScroll(AbsListView view, int firstVisibleItem,
  302. int visibleItemCount, int totalItemCount) {
  303. mFirstItemIndex = firstVisibleItem;
  304. mLastItemIndex = firstVisibleItem + visibleItemCount;
  305. }
  306. @Override
  307. public void onClick(View v) {
  308. }
  309. /***
  310. * 用于产生动画
  311. *
  312. * @author zhangjia
  313. *
  314. */
  315. private class MyAsynTask extends AsyncTask<Integer, Integer, Void> {
  316. private final static int STEP = 30;// 步伐
  317. private final static int TIME = 5;// 休眠时间
  318. private int distance;// 距离(该距离指的是:mHeadView的PaddingTop+mHeadView的高度,及默认位置状态.)
  319. private int number;// 循环执行次数.
  320. private int disPadding;// 时时padding距离.
  321. private int DRAG;
  322. @Override
  323. protected Void doInBackground(Integer... params) {
  324. try {
  325. this.DRAG = params[0];
  326. if (params[0] == DRAG_UP) {
  327. // 获取距离.
  328. distance = mHeadView.getPaddingTop()
  329. + Math.abs(mHeadViewHeight);
  330. } else {
  331. // 获取距离.
  332. distance = mFootView.getPaddingTop()
  333. + Math.abs(mHeadViewHeight);
  334. }
  335. // 获取循环次数.
  336. if (distance % STEP == 0) {
  337. number = distance / STEP;
  338. } else {
  339. number = distance / STEP + 1;
  340. }
  341. // 进行循环.
  342. for (int i = 0; i < number; i++) {
  343. Thread.sleep(TIME);
  344. publishProgress(STEP);
  345. }
  346. } catch (InterruptedException e) {
  347. e.printStackTrace();
  348. }
  349. return null;
  350. }
  351. @Override
  352. protected void onProgressUpdate(Integer... values) {
  353. super.onProgressUpdate(values);
  354. switch (DRAG) {
  355. case DRAG_UP:
  356. disPadding = Math.max(mHeadView.getPaddingTop() - STEP, -1
  357. * mHeadViewHeight);
  358. mHeadView.setPadding(0, disPadding, 0, 0);// 回归.
  359. break;
  360. case DRAG_DOWN:
  361. disPadding = Math.max(mFootView.getPaddingTop() - STEP, -1
  362. * mHeadViewHeight);
  363. mFootView.setPadding(0, disPadding, 0, 0);// 回归.
  364. break;
  365. default:
  366. break;
  367. }
  368. }
  369. }
  370. }

运行效果:

                             

默认效果                                             下拉拖拽效果(会自动回缩)               上拉拖拽效果(会自动回缩)

前面那章实现起来有点小BUG,正在处理,不过这个实现起来没有发现什么BUG,要说BUG的话,那么就是优化,因为我觉得上面效果是实现了,可是性能觉得有点差,比如说“我每次UP的时候要执行任务,那么就要创建任务对象,你想想看,每次执行都要创建,那么要创建多少对象,虽说JAVA虚拟机会自动回收,但是总觉得不是很完善,嗯,临时就如此了,自己在研究研究看.

至于微信,陌陌等大多数应用都是(数据少的话,就上下都可以拖拽,只是一个人性效果,而数据多的话,上部用于加载过时数据.下部只是个形式.),效果实现起来也不难,只是进行了些判断,效果嘛,其实上面自定义ListView整理下就OK了.

上面我详细给出了两个自定义源码的实现,大家可以直接引用.

在这里我将源码上传,如果上面看的不明白的话,你可以下载,只要你耐心看,我相信大家都能弄明白,都会进行响应扩展的.其实我们要的就是创新,而不是简单应用.

源码一

 

源码二

就说到这里,如有疑问请留言。

另外,如果对您有帮助的话,记得赞一个哦.

在此:Thanks for you !

时间: 2024-10-26 13:16:05

android ListView的上部下拉刷新下部点击加载更多具体实现及拓展的相关文章

[Android学习系列15]下拉刷新列表实现动态加载

使用google官方的SwipeRefreshLayout 参考: http://blog.csdn.net/easyer2012/article/details/22857807 http://stormzhang.github.io/android/2014/03/29/android-swiperefreshlayout/ http://www.eoeandroid.com/thread-330439-1-1.html http://www.oschina.net/translate/sw

Android ListView 下拉刷新 点击加载更多

最近项目中用到了ListView的下拉刷新的功能,总结了一下前辈们的代码,单独抽取出来写了一个demo作为示例. 效果图 下拉刷新: 加载更多: CustomListView.java [java] view plaincopy package com.example.uitest.view; import java.util.Date; import com.example.uitest.R; import android.content.Context; import android.uti

ListView实现上拉下拉刷新加载功能

第一步.首先在你项目中创建一个包存放支持下拉刷新和上拉加载的类: 第二步.需要把两个动画导入进来,实现180度旋转与360度旋转: 第三步.需要把支持的下拉与上拉显示的隐藏加载布局给导入进来 第四步.需要添加strings.xml与colors.xml文件的内容添加到项目里面: strings.xml <string name="pull_to_refresh">下拉刷新</string> <string name="release_to_ref

Ionic -- Refresher &amp; InfiniteScroll 下拉刷新与滚动懒加载

下拉刷新和滚动加载在移动端是很常见的需求,Ionic 为我们提供了开箱即用的组件.在这里我结合自己做的小demo简单介绍下. Template 在模板中需要将 ion-refresher 组件放置在 ion-content 内部的首位,将 ion-infinite-scroll 置于尾部. <ion-refresher (ionRefresh)="doRefresh($event)"> <ion-refresher-content pullingIcon="

ListView实现下拉刷新-2-将顶部布局加载到ListView中

上一篇实现了Adapter类的创建,和getView函数的分析: 这一篇主要讲第二部分,即将顶部布局加载到ListView中:重点是ReFlashListView的实现上,这一篇中我会谈一谈在阅读源代码的过程中所遇到的困难和采取的方法: 首先看ReFlashListView类: public class ReFlashListView extends ListView implements OnScrollListener 表明ReFlashListView是继承自ListView的,并且 实现

Android MVP设计框架模板 之 漂亮ListView上拉刷新下拉加载更多

mvp的全称为Model-View-Presenter,Model提供数据,View负责显示,Controller/Presenter负责逻辑的处理.MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller. 项目中大部分是面对接口编程,通过P层可以预先将所有需要的接口功能

打造Android万能上拉下拉刷新框架--XRefreshView(三)

转载请注明出处:http://blog.csdn.net/footballclub/ 打造Android万能上拉下拉刷新框架–XRefreshView(一) 打造Android万能上拉下拉刷新框架–XRefreshView(二) XRefreshView更新说明 这段时间一直有朋友给我反馈,让我帮忙解决问题,我汇总了下,有以下几种: 1. 处理listview滑动删除与XRefreshView的冲突 2. 处理viewpager和XRefreshView的冲突 3. listview滑动到底部自

react-native ListView 封装 实现 下拉刷新/上拉加载更多

1.PageListView 组件封装 src/components/PageListView/index.js /** * 上拉刷新/下拉加载更多 组件 */ import React, { Component } from 'react'; import { Text, View, ListView, FlatList, Dimensions, PanResponder, Animated, Easing, ActivityIndicator, } from 'react-native';

让Android Support V4中的SwipeRefreshLayout支持上拉加载更多

前言 原来的Android SDK中并没有下拉刷新组件,但是这个组件确实绝大多数APP必备的一个部件.好在google在v4包中出了一个SwipeRefreshLayout,但是这个组件只支持下拉刷新,不支持上拉加载更多的操作.因此,我们就来简单的扩展一下这个组件以实现上拉下载的目的. 基本原理 上拉加载或者说滚动到底部时自动加载,都是通过判断是否滚动到了ListView或者其他View的底部,然后触发相应的操作,这里我们以ListView来说明.因此我们需要在监听ListView的滚动事件,当