这个下拉效果在网上最早的例子恐怕就是Johan Nilsson的实现,http://johannilsson.com/2011/03/13/android-pull-to-refresh-update.html

如果这篇文章对您有用,劳烦几秒钟帮忙投下票:http://vote.blog.csdn.net/item/blogstar/aomandeshangxiao,Csdn 2012博客之星投票,谢谢!!!

后面的很多例子应该都是仿照这个写的,下面的这个例子就是对这个例子的修改,先看下一个点击的效果,我看到其他的分析博客里面没有谈到这一点,在这个代码中,我们一直看到是listview的第二项,而listview的第一项被遮挡了起来,滑动至第一项:

点击头条,头条会变成以下:

然后,过一段时间,刷新完成以后,listview又setSelection(1),增加一条数据,同时,把顶部给遮挡住:

这是点击刷新,然后是下拉刷新:

最后结果和点击刷新相同。那现在开始看下代码:

首先看下所用到的控件和变量:

  1. // 状态
  2. private static final int TAP_TO_REFRESH = 1;//点击刷新
  3. private static final int PULL_TO_REFRESH = 2;  //拉动刷新
  4. private static final int RELEASE_TO_REFRESH = 3; //释放刷新
  5. private static final int REFRESHING = 4;  //正在刷新
  6. // 当前滑动状态
  7. private int mCurrentScrollState;
  8. // 当前刷新状态
  9. private int mRefreshState;
  10. //头视图的高度
  11. private int mRefreshViewHeight;
  12. //头视图 原始的top padding 属性值
  13. private int mRefreshOriginalTopPadding;
  14. private int mLastMotionY;
  15. // 监听对listview的滑动动作
  16. private OnRefreshListener mOnRefreshListener;
  17. //箭头图片
  18. private static  int REFRESHICON = R.drawable.goicon;
  19. //listview 滚动监听器
  20. private OnScrollListener mOnScrollListener;
  21. private LayoutInflater mInflater;
  22. private RelativeLayout mRefreshView;
  23. //顶部刷新时出现的控件
  24. private TextView mRefreshViewText;
  25. private ImageView mRefreshViewImage;
  26. private ProgressBar mRefreshViewProgress;
  27. private TextView mRefreshViewLastUpdated;
  28. // 箭头动画效果
  29. //变为向下的箭头
  30. private RotateAnimation mFlipAnimation;
  31. //变为逆向的箭头
  32. private RotateAnimation mReverseFlipAnimation;
  33. //是否反弹
  34. private boolean mBounceHack;

看下点击刷新的代码过程:

在init()方法中初始化各个控件及设置监听:

  1. private void init(Context context) {
  2. // Load all of the animations we need in code rather than through XML
  3. mFlipAnimation = new RotateAnimation(0, -180,RotateAnimation.RELATIVE_TO_SELF,
  4. 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);
  5. mFlipAnimation.setInterpolator(new LinearInterpolator());
  6. mFlipAnimation.setDuration(250);
  7. mFlipAnimation.setFillAfter(true);
  8. mReverseFlipAnimation = new RotateAnimation(-180, 0,RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);
  9. mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
  10. mReverseFlipAnimation.setDuration(250);
  11. mReverseFlipAnimation.setFillAfter(true);
  12. mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  13. mRefreshView = (RelativeLayout) mInflater.inflate(R.layout.pull_to_refresh_header, this, false);
  14. mRefreshViewText =(TextView) mRefreshView.findViewById(R.id.pull_to_refresh_text);
  15. mRefreshViewImage =(ImageView) mRefreshView.findViewById(R.id.pull_to_refresh_image);
  16. mRefreshViewProgress =(ProgressBar) mRefreshView.findViewById(R.id.pull_to_refresh_progress);
  17. mRefreshViewLastUpdated =(TextView) mRefreshView.findViewById(R.id.pull_to_refresh_updated_at);
  18. mRefreshViewImage.setMinimumHeight(50);
  19. mRefreshView.setOnClickListener(new OnClickRefreshListener());
  20. mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();
  21. mRefreshState = TAP_TO_REFRESH;
  22. //为listview头部增加一个view
  23. addHeaderView(mRefreshView);
  24. super.setOnScrollListener(this);
  25. measureView(mRefreshView);
  26. mRefreshViewHeight = mRefreshView.getMeasuredHeight();
  27. }

我们看到,mRefreshView控件既是listview用于刷新的头控件,这里它设置了监听事件:

  1. mRefreshView.setOnClickListener(new OnClickRefreshListener());

我们再来看下监听事件的定义:

  1. private class OnClickRefreshListener implements OnClickListener {
  2. @Override
  3. public void onClick(View v) {
  4. if (mRefreshState != REFRESHING) {
  5. prepareForRefresh();
  6. onRefresh();
  7. }
  8. }
  9. }

调用了preparForRefresh()(准备刷新)和onRefresh()(刷新)两个方法,然后在查看这两个方法的定义:

  1. public void prepareForRefresh() {
  2. resetHeaderPadding();   // 恢复header的边距
  3. mRefreshViewImage.setVisibility(View.GONE);
  4. // We need this hack, otherwise it will keep the previous drawable.
  5. // 注意加上,否则仍然显示之前的图片
  6. mRefreshViewImage.setImageDrawable(null);
  7. mRefreshViewProgress.setVisibility(View.VISIBLE);
  8. // Set refresh view text to the refreshing label
  9. mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label);
  10. mRefreshState = REFRESHING;
  11. }
  12. public void onRefresh() {
  13. if (mOnRefreshListener != null) {
  14. mOnRefreshListener.onRefresh();
  15. }
  16. }

其中,后者还是回调方法。

我们看下preparForRefresh()方法中,引用了resetHeadPadding()方法:

  1. /**
  2. * Sets the header padding back to original size.
  3. * 将head的边距重置为初始的数值
  4. */
  5. private void resetHeaderPadding() {
  6. mRefreshView.setPadding(
  7. mRefreshView.getPaddingLeft(),
  8. mRefreshOriginalTopPadding,
  9. mRefreshView.getPaddingRight(),
  10. mRefreshView.getPaddingBottom());
  11. }

从新设置下header距上下左右的距离。

最重要的方法应该是:onScroll()和onTouchEvent()方法,先看下onTouchEvent()方法:

  1. @Override
  2. public boolean onTouchEvent(MotionEvent event) {
  3. //当前手指的Y值
  4. final int y = (int) event.getY();
  5. mBounceHack = false;
  6. switch (event.getAction()) {
  7. case MotionEvent.ACTION_UP:
  8. //将垂直滚动条设置为可用状态
  9. if (!isVerticalScrollBarEnabled()) {
  10. setVerticalScrollBarEnabled(true);
  11. }
  12. if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {
  13. // 拖动距离达到刷新需要
  14. if ((mRefreshView.getBottom() >= mRefreshViewHeight
  15. || mRefreshView.getTop() >= 0)
  16. && mRefreshState == RELEASE_TO_REFRESH) {
  17. // 把状态设置为正在刷新
  18. // Initiate the refresh
  19. mRefreshState = REFRESHING; //将标量设置为,正在刷新
  20. // 准备刷新
  21. prepareForRefresh();
  22. // 刷新
  23. onRefresh();
  24. } else if (mRefreshView.getBottom() < mRefreshViewHeight
  25. || mRefreshView.getTop() <= 0) {
  26. // Abort refresh and scroll down below the refresh view
  27. //停止刷新,并且滚动到头部刷新视图的下一个视图
  28. resetHeader();
  29. setSelection(1); //定位在第二个列表项
  30. }
  31. }
  32. break;
  33. case MotionEvent.ACTION_DOWN:
  34. // 获得按下y轴位置
  35. mLastMotionY = y;
  36. break;
  37. case MotionEvent.ACTION_MOVE:
  38. //更行头视图的toppadding 属性
  39. applyHeaderPadding(event);
  40. break;
  41. }
  42. return super.onTouchEvent(event);
  43. }

当按下的时候,记录按下y轴的位置,然后在move中调用了applyHeaderPadding()方法,我们再看下这个方法:

  1. // 获得header距离
  2. private void applyHeaderPadding(MotionEvent ev) {
  3. //获取累积的动作数
  4. int pointerCount = ev.getHistorySize();
  5. for (int p = 0; p < pointerCount; p++) {
  6. //如果是释放将要刷新状态
  7. if (mRefreshState == RELEASE_TO_REFRESH) {
  8. if (isVerticalFadingEdgeEnabled()) {
  9. setVerticalScrollBarEnabled(false);
  10. }
  11. //历史累积的高度
  12. int historicalY = (int) ev.getHistoricalY(p);
  13. // Calculate the padding to apply, we divide by 1.7 to
  14. // simulate a more resistant effect during pull.
  15. // 计算申请的边距,除以1.7使得拉动效果更好
  16. int topPadding = (int) (((historicalY - mLastMotionY)- mRefreshViewHeight) / 1.7);
  17. mRefreshView.setPadding(
  18. mRefreshView.getPaddingLeft(),
  19. topPadding,
  20. mRefreshView.getPaddingRight(),
  21. mRefreshView.getPaddingBottom());
  22. }
  23. }
  24. }

通过记录滑动距离,实时变化头部mRefreshView的上下左右的距离。

最后,看下手指松开的ACTION_UP:

  1. case MotionEvent.ACTION_UP:
  2. //将垂直滚动条设置为可用状态
  3. if (!isVerticalScrollBarEnabled()) {
  4. setVerticalScrollBarEnabled(true);
  5. }
  6. if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {
  7. // 拖动距离达到刷新需要
  8. if ((mRefreshView.getBottom() >= mRefreshViewHeight
  9. || mRefreshView.getTop() >= 0)
  10. && mRefreshState == RELEASE_TO_REFRESH) {
  11. // 把状态设置为正在刷新
  12. // Initiate the refresh
  13. mRefreshState = REFRESHING; //将标量设置为:正在刷新
  14. // 准备刷新
  15. prepareForRefresh();
  16. // 刷新
  17. onRefresh();
  18. } else if (mRefreshView.getBottom() < mRefreshViewHeight
  19. || mRefreshView.getTop() <= 0) {
  20. // Abort refresh and scroll down below the refresh view
  21. //停止刷新,并且滚动到头部刷新视图的下一个视图
  22. resetHeader();
  23. setSelection(1); //定位在第二个列表项
  24. }
  25. }
  26. break;

当滑动距离大于一个item的距离时,添加一个item,否则,弹回。

看完onTouchEvent(),然后再看一下onScroll()方法:

  1. @Override
  2. public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {
  3. // When the refresh view is completely visible, change the text to say
  4. // "Release to refresh..." and flip the arrow drawable.
  5. // 在refreshview完全可见时,设置文字为松开刷新,同时翻转箭头
  6. //如果是接触滚动状态,并且不是正在刷新的状态
  7. if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL&& mRefreshState != REFRESHING) {
  8. if (firstVisibleItem == 0) {
  9. //如果显示出来了第一个列表项,显示刷新图片
  10. mRefreshViewImage.setVisibility(View.VISIBLE);
  11. //如果下拉了listiview,则显示上拉刷新动画
  12. if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20|| mRefreshView.getTop() >= 0)
  13. && mRefreshState != RELEASE_TO_REFRESH) {
  14. mRefreshViewText.setText(R.string.pull_to_refresh_release_label);
  15. mRefreshViewImage.clearAnimation();
  16. mRefreshViewImage.startAnimation(mFlipAnimation);
  17. mRefreshState = RELEASE_TO_REFRESH;
  18. //如果下拉距离不够,则回归原来的状态
  19. } else if (mRefreshView.getBottom() < mRefreshViewHeight + 20
  20. && mRefreshState != PULL_TO_REFRESH) {
  21. mRefreshViewText.setText(R.string.pull_to_refresh_pull_label);
  22. if (mRefreshState != TAP_TO_REFRESH) {
  23. mRefreshViewImage.clearAnimation();
  24. mRefreshViewImage.startAnimation(mReverseFlipAnimation);
  25. }
  26. mRefreshState = PULL_TO_REFRESH;
  27. }
  28. } else {
  29. mRefreshViewImage.setVisibility(View.GONE);
  30. resetHeader();
  31. }
  32. //如果是滚动状态+ 第一个视图已经显示+ 不是刷新状态
  33. } else if (mCurrentScrollState == SCROLL_STATE_FLING  && firstVisibleItem == 0
  34. && mRefreshState != REFRESHING) {
  35. setSelection(1);
  36. mBounceHack = true;
  37. } else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {
  38. setSelection(1);
  39. }
  40. if (mOnScrollListener != null) {
  41. mOnScrollListener.onScroll(view, firstVisibleItem,visibleItemCount, totalItemCount);
  42. }
  43. }

该方法是在滑动过程中,各种状况的处理。

onScroll()方法和onTouchEvent()方法的执行过程应该是,先onTouchEvent()的ACTION_DOWN,然后是ACTION_MOVE和onScroll()方法同时进行,最后是onTouchEvent()的ACTION_UP。也可以自己打log看一下。这样在onTouchEvent()处理header,就是mRefreshView的外部的各个熟悉,onScroll()里面处理header(mRefreshView)里面内部的控件变化,从逻辑上来说比较清晰。

在onScroll()中,引用方法resetHeader()方法:

  1. /**
  2. * Resets the header to the original state.
  3. * 重置header为之前的状态
  4. */
  5. private void resetHeader() {
  6. if (mRefreshState != TAP_TO_REFRESH) {
  7. mRefreshState = TAP_TO_REFRESH;
  8. resetHeaderPadding();
  9. // 将刷新图标换成箭头
  10. // Set refresh view text to the pull label
  11. mRefreshViewText.setText(R.string.pull_to_refresh_tap_label);
  12. // Replace refresh drawable with arrow drawable
  13. // 清除动画
  14. mRefreshViewImage.setImageResource(REFRESHICON);
  15. // Clear the full rotation animation
  16. mRefreshViewImage.clearAnimation();
  17. // Hide progress bar and arrow.
  18. // 隐藏图标和进度条
  19. mRefreshViewImage.setVisibility(View.GONE);
  20. mRefreshViewProgress.setVisibility(View.GONE);
  21. }
  22. }

resetHead就是header(mRefreshView)的内部的具体操作。

当一切都完成以后,就可以调用onRefreshComplete()方法:

  1. /**
  2. * Resets the list to a normal state after a refresh.
  3. * 重置listview为普通的listview
  4. * @param lastUpdated
  5. * Last updated at.
  6. */
  7. public void onRefreshComplete(CharSequence lastUpdated) {
  8. setLastUpdated(lastUpdated);
  9. onRefreshComplete();
  10. }
  11. /**
  12. * Resets the list to a normal state after a refresh.
  13. * 重置listview为普通的listview,
  14. */
  15. public void onRefreshComplete() {
  16. resetHeader();
  17. // If refresh view is visible when loading completes, scroll down to
  18. // the next item.
  19. if (mRefreshView.getBottom() > 0) {
  20. invalidateViews();  //重绘视图
  21. setSelection(1);
  22. }
  23. }

重新绘制listivew,然后setSelection(1)。完成!

最后是源代码的下载地址:http://download.csdn.net/detail/aomandeshangxiao/4117390

还有其他两篇相关:listView下拉刷新2listView滑动刷新代码(分页功能)

来源:http://www.bozhiyue.com/anroid/boke/2016/0318/3589.html

最新文章

  1. eclipse gradle插件(buildship)的安装和使用
  2. 分享一个linux环境下快速读取行数的命令
  3. form提交的时候使用method=get导致乱码
  4. linux 目录
  5. fedora如何设置上网
  6. LCD_ILI9320横竖屏方向的问题。
  7. Apache配置允许文件索引
  8. 安装 vsftp
  9. 无法更新 EntitySet“Ips_Articles”,因为它有一个 DefiningQuery,而 &lt;ModificationFunctionMapping&gt; 元素中没有支持当前操作的 &lt;InsertFunction&gt; 元素。
  10. HTML5 总结-表单-输入类型
  11. jsp连接MySQL数据库显示GIS地理数据乱码问题的解决(select AsText(the_geom))
  12. find查找命令
  13. springMVC中文乱码问题解决
  14. Golang微服务:micro实践
  15. 将自己的SpringBoot应用打包发布到Linux下Docker中
  16. Resource Agent:LSB和OCF
  17. 自己的vim配置
  18. How to update XENTRY Connect C5 software with .iso file
  19. Mac编译安装swfTools
  20. Deep Reinforcement Learning from Self-Play in Imperfect-Information Games

热门文章

  1. hdu 5289 Assignment(2015多校第一场第2题)RMQ+二分(或者multiset模拟过程)
  2. HDU 5690 All X 数学
  3. SlickSafe.NET 开源权限框架开发指南
  4. DOM事件绑定方式
  5. C#程序集系列02,使用记事本查看可执行程序集的IL代码
  6. 系统字体的Regular、Light等几种名称的区别
  7. bt z
  8. Selenium2+python自动化44-元素定位参数化(find_element)
  9. eclipse 开发web程序,启动tomcat插件服务器的时候。部署目录在那里?
  10. linux usb信息查看与调试