总结和分析几种判断 RecyclerView 到达底部的方法

2018-08-24 11:33:17

 用事件分发的原理结合 SwipeRefreshLayout 写一个 RecyclerView 的上下拉 ,里面有一个判断 RecyclerView 是否到达底部的方法 isBottom。我的同事用了这个上下拉之后发现有些小 bug,没考虑周全,譬如各个子项高度不统一的时候,然后我找到原因是因为这个判断上下拉的问题。所以,我就去网上查到几种判断 RecyclerView 到达底部的方法,发现各有千秋。以下的分析都以上一篇文章的 SwipeRecyclerView 为例。

1.lastVisibleItemPosition == totalItemCount - 1 判断;
2.computeVerticalScrollRange() 等三个方法判断;
3.canScrollVertically(1) 判断;
4.利用 RecyclerView 的 LinearLayoutManager 几个方法判断。1234
  • 其实,第2和第3种是属于同一种方法,在下面的分析会讲到。

第一种方法:

这种方法也是网上最多人用的方法,我们来分析一下:

public static boolean isVisBottom(RecyclerView recyclerView){  
  LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();  
  //屏幕中最后一个可见子项的 position
  int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();  
  //当前屏幕所看到的子项个数
  int visibleItemCount = layoutManager.getChildCount();  
  //当前 RecyclerView 的所有子项个数
  int totalItemCount = layoutManager.getItemCount();  
  //RecyclerView 的滑动状态
  int state = recyclerView.getScrollState();  
  if(visibleItemCount > 0 && lastVisibleItemPosition == totalItemCount - 1 && state == recyclerView.SCROLL_STATE_IDLE){   
     return true; 
  }else {   
     return false;  
  }
}12345678910111213141516

很明显,当屏幕中最后一个子项 lastVisibleItemPosition 等于所有子项个数 totalItemCount - 1,那么 RecyclerView 就到达了底部。但是,我在这种方法中发现了极为极端的情况,就是当 totalItemCount 等于1,而这个子项的高度比屏幕还要高。

这里写图片描述

我们可以发现这个子项没完全显示出来就已经被判断为拉到底部。当然,这种方法一般情况下都能满足开发者的需求,只是遇到了强迫症的我~

第二种方法:

public static boolean isSlideToBottom(RecyclerView recyclerView) {    
   if (recyclerView == null) return false; 
   if (recyclerView.computeVerticalScrollExtent() + recyclerView.computeVerticalScrollOffset() 
        >= recyclerView.computeVerticalScrollRange())   
     return true;  
   return false;
}
这种方法原理其实很简单,而且也是 View 自带的方法。12345678

原理图

这样就很清晰明了,computeVerticalScrollExtent() 是当前屏幕显示的区域高度,computeVerticalScrollOffset() 是当前屏幕之前滑过的距离,而 computeVerticalScrollRange() 是整个 View 控件的高度。

这种方法经过测试,暂时还没发现有 bug,而且它用的是 View 自带的方法,所以个人觉得比较靠谱。

第三种方法:

RecyclerView.canScrollVertically(1) 的值表示是否能向上滚动,false 表示已经滚动到底部 
RecyclerView.canScrollVertically(-1) 的值表示是否能向下滚动,false 表示已经滚动到顶部 
这种方法更简单,就通过简单的调用方法,就可以得到你想要的结果。我一讲过这种方法与第二种方法其实是同一种方法,那下面来分析一下,看看 canScrollVertically 的源码:

canScrollVertically的源码

是不是一目鸟然了,canScrollVertically 方法的实现实际上运用到的是方法二的三个函数,只是这个方法 Android 已经帮我们封装好了,原理一模一样的。

本人现在也是运用了这种方法做判断的~懒人~工具类都省了~

第四种方法:

我们可以使用 LinearLayoutManager得几个方法: 
1. 算出已经滑过的子项的距离。 
2. 算出屏幕的高度。 
3. 算出 RecyclerView 的总高度。然后用他们做比较,原理类似于方法二。

public static int getItemHeight(RecyclerView recyclerView) {  
  int itemHeight = 0;  
  View child = null;  
  LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();  
  int firstPos = layoutManager.findFirstCompletelyVisibleItemPosition(); 
  int lastPos = layoutManager.findLastCompletelyVisibleItemPosition();  
  child = layoutManager.findViewByPosition(lastPos);  
  if (child != null) {   
     RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();   
     itemHeight = child.getHeight() + params.topMargin + params.bottomMargin;  
  }   
 return itemHeight;}//算出一个子项的高度public static int getLinearScrollY(RecyclerView recyclerView) {  
  int scrollY = 0;  
  LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();  
  int headerCildHeight = getHeaderHeight(recyclerView);  
  int firstPos = layoutManager.findFirstVisibleItemPosition();  
  View child = layoutManager.findViewByPosition(firstPos);  
  int itemHeight = getItemHeight(recyclerView);  
  if (child != null) {   
     int firstItemBottom = layoutManager.getDecoratedBottom(child);   
     scrollY = headerCildHeight + itemHeight * firstPos - firstItemBottom;    
     if(scrollY < 0){    
         scrollY = 0;    
     }  
  }  
  return scrollY;
}//算出滑过的子项的总距离public static int getLinearTotalHeight(RecyclerView recyclerView) {    int totalHeight = 0;  
  LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();  
  View child = layoutManager.findViewByPosition(layoutManager.findFirstVisibleItemPosition());  
  int headerCildHeight = getHeaderHeight(recyclerView);  
  if (child != null) {   
     int itemHeight = getItemHeight(recyclerView);    
     int childCount = layoutManager.getItemCount();    
     totalHeight = headerCildHeight + (childCount - 1) * itemHeight;  
  }  
  return totalHeight;
}//算出所有子项的总高度public static boolean isLinearBottom(RecyclerView recyclerView) {    
boolean isBottom = true;  
  int scrollY = getLinearScrollY(recyclerView);  
  int totalHeight = getLinearTotalHeight(recyclerView); 
  int height = recyclerView.getHeight(); //    Log.e("height","scrollY  " + scrollY + "  totalHeight  " +  totalHeight + "  recyclerHeight  " + height);  
  if (scrollY + height < totalHeight) {    
    isBottom = false;  
  }  
  return isBottom;
}//高度作比较123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657

虽然这种方法看上去比较呆板的同时考虑不很周全,但这种方法可以对RecylerView的LinearLayoutManager有深一步的理解,这也是我的师兄给我提供的一个借鉴的类,我非常感谢他!有兴趣的同学可以去下载源码的做进一步的研究,发现有更好玩的方法可以一起研究!

  • 2019-03-15 15:28:33

    10分钟教你搭建自己的ngrok服务器

    内网穿透想必开发过微信的同志都很了解,大部分人选择网上寻找各种现成的,比如ngrok官网、ittun-ngrok、sunny-ngrok或者花生壳之类的。但是世界上没有免费的午餐,要不就是收费,要不就是免费但是偶尔会出现连接失败的问题(当然大多数时间是没有问题的)。

  • 2019-03-15 15:29:53

    丢弃花生壳,搭建自己的ngrok作为内网穿透服务器

    公司没有公网,公司在二级路由下面(就是服务商没有给独立IP,也无法动态获取公网IP),然而公司在开发程序的时候,如对接微信等需要返回数据,或者需要别人能访问我们网址,一般我们就只能购买花生壳内网穿透,但是在使用中发现速度慢,经常掉。所以搭建自己的ngrok服务器来保证内网穿透。

  • 2019-03-15 15:31:21

    内网穿透 ngrok 服务器和客户端配置

    ngrok 简介及作用 ngrok 是一款用 go 语言开发的开源软件,它是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。下图简述了 ngrok 的原理。

  • 2019-03-15 15:32:09

    内网打洞以及代码实现

    假设内网的多个ip或者同一ip的不同port,都要访问同一个(外网ip:port)。对NAT来说,从外网接收的包它的(srcIp:srcPort)==(serverIp,serverPort),它的dstIp==natIp,所以NAT只能用dstPort来决定把这个包转发给哪一个(内网Ip:port)

  • 2019-03-15 15:33:08

    Xshell不能按退格、删除键的解决方案

    在使用xshell时,由于每个服务器不同,一些无法使用Backspace键向后删除字符。针对这个问题,本文为大家解答下退格键无法识别如何设置?

  • 2019-03-15 15:49:28

    win10远程桌面连接不上解决方法

    有朋友就感叹电脑的世界真的是很神奇,可以将整个世界连接在一起。如果别人想要摆弄你的电脑,即使不在一个地方也可以利用远程桌面来控制。而这就是所谓的远程控制操作了,大部分人都知道它的作用,不过这也不排除会遇到一些突发情况的时候,例如win10远程桌面连接不上,这该怎么去解决呢?为此,小编给大家带来了解决的图文操作。

  • 2019-03-15 16:49:18

    Win7无法进入家庭组提示“您的系统管理员不允许访问家庭组”怎么办

     家庭组是家庭网络上可以共享文件和打印机的一组计算机,可以方便用户们共享文件或者视频等,可是最近有win7纯净版系统用户却发现无法进入家庭组,提示“您的系统管理员不允许访问家庭组”,该怎么办呢?现在给大家分享一下Win7无法进入家庭组提示“您的系统管理员不允许访问家庭组”的解决方法。

  • 2019-03-17 22:19:28

    动态更新Toolbar Menu以及Menu中同时显示文字和图标

    我们经常会有这样的需求,在切换Fragment或者点击某个按钮后动态更新Toolbar上Menu项.但是onCreateOptionsMenu方法只在创建Activity的时候调用一次,以后就不再调用了,所以就不能在onCreateOptionsMenu中做处理了。 不过系统提供了另外的一个方法onPrepa