开源Android-PullToRefresh下拉刷新源码分析

标签:Android布局下拉刷新pullToRefresh源码 2448人阅读 评论(1) 收藏 举报 分类: Android 技术研究(39)

PullToRefresh 这个库用的是非常至多,github 今天主要分析一下源码实现.

我们通过ListView的下拉刷新进行分析,其它的类似。

整个下拉刷新 父View是LinearLayout, 在LinearLayout添加了Header View ,Footer View,和ListView

PullToRefreshBase 是父类 扩展了 LinearLayout水平布局 如果我们使用ListView 需要观看子类 PullToRefreshAdapterViewBase -> PullToRefreshListView


初始化代码在PullToRefreshBase init方法中

重点代码:

[java] view plain copy
  1. //RefreshableView
  2. //Bypassingtheattrs,wecanaddListView/GridViewparamsviaXML
  3. mRefreshableView=createRefreshableView(context,attrs);//通过子类传入的View,ListView或者ScrollView等
  4. addRefreshableView(context,mRefreshableView);//添加view到布局中
  5. //Weneedtocreatenowlayoutsnow创建Header和Footer视图,默认是INVISIBLE,要添加到父窗口
  6. mHeaderLayout=createLoadingLayout(context,Mode.PULL_FROM_START,a);
  7. mFooterLayout=createLoadingLayout(context,Mode.PULL_FROM_END,a);
  8. handleStyledAttributes(a);//添加loadingView效果,这里是把View添加到ListViewHeaderView里面去
  9. updateUIForMode();//把布局添加到父View中

[java] view plain copy
  1. protectedvoidupdateUIForMode(){
  2. finalLinearLayout.LayoutParamslp=getLoadingLayoutLayoutParams();
  3. //RemoveHeader,andthenaddHeaderLoadingViewagainifneeded
  4. if(this==mHeaderLayout.getParent()){
  5. removeView(mHeaderLayout);
  6. }
  7. if(mMode.showHeaderLoadingLayout()){
  8. addViewInternal(mHeaderLayout,0,lp);//加入View到LinearLayout
  9. }
  10. //RemoveFooter,andthenaddFooterLoadingViewagainifneeded
  11. if(this==mFooterLayout.getParent()){
  12. removeView(mFooterLayout);
  13. }
  14. if(mMode.showFooterLoadingLayout()){
  15. addViewInternal(mFooterLayout,lp);//加入View到LinearLayout
  16. }
  17. //HideLoadingViews
  18. refreshLoadingViewsSize();//把headerView隐藏起来,其实用的是padding的方式设置为负值就到屏幕顶部的外面了
  19. //Ifwe'renotusingMode.BOTH,setmCurrentModetomMode,otherwise
  20. //setittopulldown
  21. mCurrentMode=(mMode!=Mode.BOTH)?mMode:Mode.PULL_FROM_START;
  22. }

//这里有2个LoadingView,一个是加入到LinearLayout中去了,还有一个是加入到ListView本身的Header里面

看看handleStyledAttributes方法 定位到子类复写的地方

FrameLayout frame = new FrameLayout(getContext());
mHeaderLoadingView = createLoadingLayout(getContext(), Mode.PULL_FROM_START, a);
mHeaderLoadingView.setVisibility(View.GONE);
frame.addView(mHeaderLoadingView, lp);
mRefreshableView.addHeaderView(frame, null, false);//添加LoadingView到ListView Header上

//headerView一共有2个LoadingView,一个是被加入到LinearLayout一个是被加入到ListView的HeaderView
addViewInternal方法就是加入到LinearLayout父类中


看看LoadingLayout 有2种 FlipLoadingLayout 和 RotateLoadingLayout 一般我们用旋转的加载动画
左边一个旋转图片,右边是文字和时间提示

第一个LoadingLayout主要显示 :下拉刷新,放开以刷新
第二个LoadingLayout显示松手后的文字:正在载入...

结构是这样



当UI初始化好,下面看看onTouch 下拉捕获事件


[java] view plain copy
  1. publicfinalbooleanonTouchEvent(MotionEventevent){
  2. if(!isPullToRefreshEnabled()){
  3. returnfalse;
  4. }
  5. //Ifwe'rerefreshing,andtheflagisset.Eattheevent
  6. if(!mScrollingWhileRefreshingEnabled&&isRefreshing()){
  7. returntrue;
  8. }
  9. if(event.getAction()==MotionEvent.ACTION_DOWN&&event.getEdgeFlags()!=0){
  10. returnfalse;
  11. }
  12. switch(event.getAction()){
  13. caseMotionEvent.ACTION_MOVE:{
  14. if(mIsBeingDragged){
  15. mLastMotionY=event.getY();
  16. mLastMotionX=event.getX();
  17. pullEvent();//开始下拉,移动
  18. returntrue;
  19. }
  20. break;
  21. }
  22. caseMotionEvent.ACTION_DOWN:{
  23. if(isReadyForPull()){//按下开始下拉
  24. mLastMotionY=mInitialMotionY=event.getY();
  25. mLastMotionX=mInitialMotionX=event.getX();
  26. returntrue;
  27. }
  28. break;
  29. }
  30. caseMotionEvent.ACTION_CANCEL:
  31. caseMotionEvent.ACTION_UP:{//停止下拉的时候
  32. if(mIsBeingDragged){
  33. mIsBeingDragged=false;
  34. if(mState==State.RELEASE_TO_REFRESH
  35. &&(null!=mOnRefreshListener||null!=mOnRefreshListener2)){
  36. setState(State.REFRESHING,true);//放下手指开始回调,执行我们的回调任务
  37. returntrue;
  38. }
  39. //Ifwe'realreadyrefreshing,justscrollbacktothetop
  40. if(isRefreshing()){
  41. smoothScrollTo(0);
  42. returntrue;
  43. }
  44. //Ifwehaven'treturnedbyhere,thenwe'renotinastate
  45. //topull,sojustreset
  46. setState(State.RESET);//恢复到原来的UI状态
  47. returntrue;
  48. }
  49. break;
  50. }
  51. }
  52. returnfalse;
  53. }


[java] view plain copy
  1. 看看pullEvent方法
  2. privatevoidpullEvent(){
  3. finalintnewScrollValue;
  4. finalintitemDimension;
  5. finalfloatinitialMotionValue,lastMotionValue;
  6. switch(getPullToRefreshScrollDirection()){
  7. caseHORIZONTAL:
  8. initialMotionValue=mInitialMotionX;
  9. lastMotionValue=mLastMotionX;
  10. break;
  11. caseVERTICAL:
  12. default:
  13. initialMotionValue=mInitialMotionY;
  14. lastMotionValue=mLastMotionY;
  15. break;
  16. }
  17. //计算下拉移动了多少
  18. switch(mCurrentMode){
  19. casePULL_FROM_END://上拉
  20. newScrollValue=Math.round(Math.max(initialMotionValue-lastMotionValue,0)/FRICTION);
  21. itemDimension=getFooterSize();
  22. break;
  23. casePULL_FROM_START://下拉
  24. default:
  25. newScrollValue=Math.round(Math.min(initialMotionValue-lastMotionValue,0)/FRICTION);
  26. itemDimension=getHeaderSize();
  27. break;
  28. }
  29. //显示HeaderView得到移动的值,可以让LoadingView显示出来
  30. setHeaderScroll(newScrollValue);
  31. if(newScrollValue!=0&&!isRefreshing()){
  32. floatscale=Math.abs(newScrollValue)/(float)itemDimension;
  33. switch(mCurrentMode){
  34. casePULL_FROM_END:
  35. mFooterLayout.onPull(scale);
  36. break;
  37. casePULL_FROM_START:
  38. default:
  39. mHeaderLayout.onPull(scale);//旋转左边的加载图片,显示文字和图片这个地方最终会执行LoadingLayout中的onPullImpl方法
  40. break;
  41. }
  42. //更新状态包括2中释放按下触摸,还有就是没释放手的触摸
  43. if(mState!=State.PULL_TO_REFRESH&&itemDimension>=Math.abs(newScrollValue)){
  44. setState(State.PULL_TO_REFRESH);
  45. }elseif(mState==State.PULL_TO_REFRESH&&itemDimension<Math.abs(newScrollValue)){
  46. setState(State.RELEASE_TO_REFRESH);//下拉松手可以松手了
  47. }
  48. }
  49. }



[java] view plain copy
  1. 再看看setHeaderScroll方法代码
  2. protectedfinalvoidsetHeaderScroll(intvalue){
  3. if(DEBUG){
  4. Log.d(LOG_TAG,"setHeaderScroll:"+value);
  5. }
  6. if(DEBUG){
  7. Log.d(LOG_TAG,"setHeaderScroll:"+value);
  8. }
  9. //Clampvaluetowithpullscrollrange
  10. finalintmaximumPullScroll=getMaximumPullScroll();
  11. value=Math.min(maximumPullScroll,Math.max(-maximumPullScroll,value));
  12. if(mLayoutVisibilityChangesEnabled){
  13. if(value<0){//有位移才显示
  14. mHeaderLayout.setVisibility(View.VISIBLE);
  15. }elseif(value>0){<spanstyle="font-family:Arial,Helvetica,sans-serif;">//有位移才显示</span>
  16. mFooterLayout.setVisibility(View.VISIBLE);
  17. }else{
  18. mHeaderLayout.setVisibility(View.INVISIBLE);
  19. mFooterLayout.setVisibility(View.INVISIBLE);
  20. }
  21. }
  22. if(USE_HW_LAYERS){
  23. /**
  24. *UseaHardwareLayerontheRefreshableViewifwe'vescrolledat
  25. *all.Wedon'tusethemontheHeader/FooterViewsastheychange
  26. *often,whichwouldnegateanyHWlayerperformanceboost.
  27. */
  28. ViewCompat.setLayerType(mRefreshableViewWrapper,value!=0?View.LAYER_TYPE_HARDWARE
  29. :View.LAYER_TYPE_NONE);
  30. }
  31. //回到最原始的scrollTo最常用的移动布局
  32. switch(getPullToRefreshScrollDirection()){
  33. caseVERTICAL:
  34. scrollTo(0,value);
  35. break;
  36. caseHORIZONTAL:
  37. scrollTo(value,0);
  38. break;
  39. }
  40. }

setState(State.REFRESHING, true);//拉倒最顶部 松手,会执行onRefreshing方法,回调我们实现的任务接口 也就是OnRefreshListener


[java] view plain copy
  1. protectedvoidonRefreshing(finalbooleandoScroll){
  2. if(mMode.showHeaderLoadingLayout()){
  3. mHeaderLayout.refreshing();
  4. }
  5. if(mMode.showFooterLoadingLayout()){
  6. mFooterLayout.refreshing();
  7. }
  8. if(doScroll){
  9. if(mShowViewWhileRefreshing){
  10. //CallRefreshListenerwhentheScrollhasfinished
  11. OnSmoothScrollFinishedListenerlistener=newOnSmoothScrollFinishedListener(){
  12. @Override
  13. publicvoidonSmoothScrollFinished(){
  14. callRefreshListener();//回调接口执行
  15. }
  16. };
  17. switch(mCurrentMode){
  18. caseMANUAL_REFRESH_ONLY:
  19. casePULL_FROM_END:
  20. smoothScrollTo(getFooterSize(),listener);
  21. break;
  22. default:
  23. casePULL_FROM_START:
  24. smoothScrollTo(-getHeaderSize(),listener);
  25. break;
  26. }
  27. }else{
  28. smoothScrollTo(0);//回到原来的位置
  29. }
  30. }else{
  31. //We'renotscrolling,sojustcallRefreshListenernow
  32. callRefreshListener();//回调接口执行
  33. }
  34. }



[java] view plain copy
  1. privatevoidcallRefreshListener(){
  2. if(null!=mOnRefreshListener){
  3. mOnRefreshListener.onRefresh(this);//回调
  4. }elseif(null!=mOnRefreshListener2){//这个是上拉,下拉都可以的情况,使用onRefreshListener2
  5. if(mCurrentMode==Mode.PULL_FROM_START){
  6. mOnRefreshListener2.onPullDownToRefresh(this);
  7. }elseif(mCurrentMode==Mode.PULL_FROM_END){
  8. mOnRefreshListener2.onPullUpToRefresh(this);
  9. }
  10. }
  11. }

总结:状态包括下拉刷新,松手刷新,正在刷新,Loading隐藏。移动UI还是用的scrollTo最基本的代码. 动画部分可以看LoadingLayout的2个子类

主要的就这些,还有很多细节没有分析。若有问题请指出谢谢。


更多相关文章

  1. Android5.1 SystemUI 启动流程
  2. SwipeRefreshLayout和ListView的EmptyView共存冲突的问题
  3. android 事件分发,解决由于listview中实时刷新,导致子view点击事件
  4. Android(安卓)studio多个项目之间怎么实现快速切换?
  5. android webview js交互之自定义错误加载界面(重新刷新)
  6. [置顶] Android(安卓)popwindow和fragment结合 左侧弹出下拉菜单
  7. android下拉五级菜单联动
  8. Android(安卓)UI之实现Material化的下拉选择列表拒绝丑陋的Spinn
  9. android禁止状态栏下拉

随机推荐

  1. 0322作业
  2. c语言指针学习
  3. 盒模型box-sizing功能及相对定位、绝对定
  4. CSS中flex布局的属性及应用
  5. 0319表格和表单
  6. Mysql
  7. mysql
  8. 0319作业
  9. SMTP 协议入门教程
  10. 一些Java开发人员在编程中常见的雷!