`
923723914
  • 浏览: 632343 次
文章分类
社区版块
存档分类
最新评论

listView下拉刷新(仿sina微博Android客户端效果)

 
阅读更多

这个下拉效果在网上最早的例子恐怕就是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. privatestaticfinalintTAP_TO_REFRESH=1;//点击刷新
  3. privatestaticfinalintPULL_TO_REFRESH=2;//拉动刷新
  4. privatestaticfinalintRELEASE_TO_REFRESH=3;//释放刷新
  5. privatestaticfinalintREFRESHING=4;//正在刷新
  6. //当前滑动状态
  7. privateintmCurrentScrollState;
  8. //当前刷新状态
  9. privateintmRefreshState;
  10. //头视图的高度
  11. privateintmRefreshViewHeight;
  12. //头视图原始的toppadding属性值
  13. privateintmRefreshOriginalTopPadding;
  14. privateintmLastMotionY;
  15. //监听对listview的滑动动作
  16. privateOnRefreshListenermOnRefreshListener;
  17. //箭头图片
  18. privatestaticintREFRESHICON=R.drawable.goicon;
  19. //listview滚动监听器
  20. privateOnScrollListenermOnScrollListener;
  21. privateLayoutInflatermInflater;
  22. privateRelativeLayoutmRefreshView;
  23. //顶部刷新时出现的控件
  24. privateTextViewmRefreshViewText;
  25. privateImageViewmRefreshViewImage;
  26. privateProgressBarmRefreshViewProgress;
  27. privateTextViewmRefreshViewLastUpdated;
  28. //箭头动画效果
  29. //变为向下的箭头
  30. privateRotateAnimationmFlipAnimation;
  31. //变为逆向的箭头
  32. privateRotateAnimationmReverseFlipAnimation;
  33. //是否反弹
  34. privatebooleanmBounceHack;
看下点击刷新的代码过程:

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

  1. privatevoidinit(Contextcontext){
  2. //LoadalloftheanimationsweneedincoderatherthanthroughXML
  3. mFlipAnimation=newRotateAnimation(0,-180,RotateAnimation.RELATIVE_TO_SELF,
  4. 0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
  5. mFlipAnimation.setInterpolator(newLinearInterpolator());
  6. mFlipAnimation.setDuration(250);
  7. mFlipAnimation.setFillAfter(true);
  8. mReverseFlipAnimation=newRotateAnimation(-180,0,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
  9. mReverseFlipAnimation.setInterpolator(newLinearInterpolator());
  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(newOnClickRefreshListener());
  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(newOnClickRefreshListener());

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

  1. privateclassOnClickRefreshListenerimplementsOnClickListener{
  2. @Override
  3. publicvoidonClick(Viewv){
  4. if(mRefreshState!=REFRESHING){
  5. prepareForRefresh();
  6. onRefresh();
  7. }
  8. }
  9. }

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

  1. publicvoidprepareForRefresh(){
  2. resetHeaderPadding();//恢复header的边距
  3. mRefreshViewImage.setVisibility(View.GONE);
  4. //Weneedthishack,otherwiseitwillkeepthepreviousdrawable.
  5. //注意加上,否则仍然显示之前的图片
  6. mRefreshViewImage.setImageDrawable(null);
  7. mRefreshViewProgress.setVisibility(View.VISIBLE);
  8. //Setrefreshviewtexttotherefreshinglabel
  9. mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label);
  10. mRefreshState=REFRESHING;
  11. }
  12. publicvoidonRefresh(){
  13. if(mOnRefreshListener!=null){
  14. mOnRefreshListener.onRefresh();
  15. }
  16. }

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

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

  1. /**
  2. *Setstheheaderpaddingbacktooriginalsize.
  3. *将head的边距重置为初始的数值
  4. */
  5. privatevoidresetHeaderPadding(){
  6. mRefreshView.setPadding(
  7. mRefreshView.getPaddingLeft(),
  8. mRefreshOriginalTopPadding,
  9. mRefreshView.getPaddingRight(),
  10. mRefreshView.getPaddingBottom());
  11. }

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

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

  1. @Override
  2. publicbooleanonTouchEvent(MotionEventevent){
  3. //当前手指的Y值
  4. finalinty=(int)event.getY();
  5. mBounceHack=false;
  6. switch(event.getAction()){
  7. caseMotionEvent.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. //Initiatetherefresh
  19. mRefreshState=REFRESHING;//将标量设置为,正在刷新
  20. //准备刷新
  21. prepareForRefresh();
  22. //刷新
  23. onRefresh();
  24. }elseif(mRefreshView.getBottom()<mRefreshViewHeight
  25. ||mRefreshView.getTop()<=0){
  26. //Abortrefreshandscrolldownbelowtherefreshview
  27. //停止刷新,并且滚动到头部刷新视图的下一个视图
  28. resetHeader();
  29. setSelection(1);//定位在第二个列表项
  30. }
  31. }
  32. break;
  33. caseMotionEvent.ACTION_DOWN:
  34. //获得按下y轴位置
  35. mLastMotionY=y;
  36. break;
  37. caseMotionEvent.ACTION_MOVE:
  38. //更行头视图的toppadding属性
  39. applyHeaderPadding(event);
  40. break;
  41. }
  42. returnsuper.onTouchEvent(event);
  43. }

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

  1. //获得header距离
  2. privatevoidapplyHeaderPadding(MotionEventev){
  3. //获取累积的动作数
  4. intpointerCount=ev.getHistorySize();
  5. for(intp=0;p<pointerCount;p++){
  6. //如果是释放将要刷新状态
  7. if(mRefreshState==RELEASE_TO_REFRESH){
  8. if(isVerticalFadingEdgeEnabled()){
  9. setVerticalScrollBarEnabled(false);
  10. }
  11. //历史累积的高度
  12. inthistoricalY=(int)ev.getHistoricalY(p);
  13. //Calculatethepaddingtoapply,wedivideby1.7to
  14. //simulateamoreresistanteffectduringpull.
  15. //计算申请的边距,除以1.7使得拉动效果更好
  16. inttopPadding=(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. caseMotionEvent.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. //Initiatetherefresh
  13. mRefreshState=REFRESHING;//将标量设置为:正在刷新
  14. //准备刷新
  15. prepareForRefresh();
  16. //刷新
  17. onRefresh();
  18. }elseif(mRefreshView.getBottom()<mRefreshViewHeight
  19. ||mRefreshView.getTop()<=0){
  20. //Abortrefreshandscrolldownbelowtherefreshview
  21. //停止刷新,并且滚动到头部刷新视图的下一个视图
  22. resetHeader();
  23. setSelection(1);//定位在第二个列表项
  24. }
  25. }
  26. break;

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

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

  1. @Override
  2. publicvoidonScroll(AbsListViewview,intfirstVisibleItem,intvisibleItemCount,inttotalItemCount){
  3. //Whentherefreshviewiscompletelyvisible,changethetexttosay
  4. //"Releasetorefresh..."andflipthearrowdrawable.
  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. }elseif(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. }elseif(mCurrentScrollState==SCROLL_STATE_FLING&&firstVisibleItem==0
  34. &&mRefreshState!=REFRESHING){
  35. setSelection(1);
  36. mBounceHack=true;
  37. }elseif(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. *Resetstheheadertotheoriginalstate.
  3. *重置header为之前的状态
  4. */
  5. privatevoidresetHeader(){
  6. if(mRefreshState!=TAP_TO_REFRESH){
  7. mRefreshState=TAP_TO_REFRESH;
  8. resetHeaderPadding();
  9. //将刷新图标换成箭头
  10. //Setrefreshviewtexttothepulllabel
  11. mRefreshViewText.setText(R.string.pull_to_refresh_tap_label);
  12. //Replacerefreshdrawablewitharrowdrawable
  13. //清除动画
  14. mRefreshViewImage.setImageResource(REFRESHICON);
  15. //Clearthefullrotationanimation
  16. mRefreshViewImage.clearAnimation();
  17. //Hideprogressbarandarrow.
  18. //隐藏图标和进度条
  19. mRefreshViewImage.setVisibility(View.GONE);
  20. mRefreshViewProgress.setVisibility(View.GONE);
  21. }
  22. }

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

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

  1. /**
  2. *Resetsthelisttoanormalstateafterarefresh.
  3. *重置listview为普通的listview
  4. *@paramlastUpdated
  5. *Lastupdatedat.
  6. */
  7. publicvoidonRefreshComplete(CharSequencelastUpdated){
  8. setLastUpdated(lastUpdated);
  9. onRefreshComplete();
  10. }
  11. /**
  12. *Resetsthelisttoanormalstateafterarefresh.
  13. *重置listview为普通的listview,
  14. */
  15. publicvoidonRefreshComplete(){
  16. resetHeader();
  17. //Ifrefreshviewisvisiblewhenloadingcompletes,scrolldownto
  18. //thenextitem.
  19. if(mRefreshView.getBottom()>0){
  20. invalidateViews();//重绘视图
  21. setSelection(1);
  22. }
  23. }
重新绘制listivew,然后setSelection(1)。完成!

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

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


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics