热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android中RecyclerView实现分页滚动的方法详解

RecyclerView实现滚动相信对大家来说都不陌生,但是本文主要给大家介绍了利用Android中RecyclerView实现分页滚动的思路和方法,可以实现翻页功能,一次翻一页,也可以实现翻至某一页功能。文中给出了详细的示例代码,需要的朋友可以参考借鉴,下面来一起看看吧。

一、需求分析

最近公司项目要实现一个需求要满足以下功能:

      1)显示一个 list 列表, item 数量不固定。

      2)实现翻页功能,一次翻一页。

      3)实现翻至某一页功能。

下面介绍通过 RecyclerView 实现该需求的实现过程(效果图如下)。

二、功能实现

2.1 OnTouchListener 记录当前开始滑动位置

要实现翻页滑动首先我们要确定是向前翻页还是向后翻页,这里通过记录开始翻页前当前的位置和滑动后的位置比较即可得知,下面选择手指触摸按下时滑动的位置为当前开始滑动位置:

 //当前滑动距离
 private int offsetY = 0;
 private int offsetX = 0;
 //按下屏幕点
 private int startY = 0;
 private int startX = 0;
@Override
  public boolean onTouch(View v, MotionEvent event) {
   //手指按下的时候记录开始滚动的坐标
   if (event.getAction() == MotionEvent.ACTION_DOWN) {
    //手指按下的开始坐标
    startY = offsetY;
    startX = offsetX;
   }
   return false;
  }
 }

好了,当我们确定了滑动方向,下面要考虑的就是如何实现滑动?

2.2 scrollTo(int x, int y) 和 scrollBy(int x, int y) 实现滑动

滑动我们最容易想到的方法就是 scrollTo(int x, int y)scrollBy(int x, int y) 这两个方法, scrollTo(int x, int y) 是将当前 View 的内容滑动至某一位置, scrollBy(int x, int y) 是将当前 View 内容相对于当前位置滑动一定的距离,其实 scrollBy(int x, int y) 内部是调用了 scrollTo(int x, int y) 方法实现的 一开始想用 scrollTo(int x, int y) 去实现,但是简单看了下源码发现, RecyclerView 不支持这个方法:

@Override
 public void scrollTo(int x, int y) {
  Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. "
    + "Use scrollToPosition instead");
 }

所以这里我们就选择使用 scrollBy(int x, int y) 去实现滑动。

2.3 OnFlingListener 和 OnScrollListener 调用滑动时机

上面我们决定使用 scrollBy(int x, int y) 去实现滑动,那么现在我们就要确定 scrollBy(int x, int y) 这个方法的调用时机,我们知道当我们滑动 RecyclerView 的时候一般分为两种情况,一种是手指在屏幕上面缓慢滑动(Scroll),另一种是飞速滑动(onFling),经过一番查阅,发现 RecyclerView 中有这两种状态的监听,那么我们一起看一下这两种状态的方法定义,先看 onFling(int velocityX, int velocityY)

Note: 由于使用了 RecyclerView 的 OnFlingListener,所以 RecycleView 的版本必须要 recyclerview-v7:25.0.0 以上。

/**
 * This class defines the behavior of fling if the developer wishes to handle it.
 * 

* Subclasses of {@link OnFlingListener} can be used to implement custom fling behavior. * * @see #setOnFlingListener(OnFlingListener) */ public static abstract class OnFlingListener { /** * Override this to handle a fling given the velocities in both x and y directions. * Note that this method will only be called if the associated {@link LayoutManager} * supports scrolling and the fling is not handled by nested scrolls first. * * @param velocityX the fling velocity on the X axis * @param velocityY the fling velocity on the Y axis * * @return true if the fling washandled, false otherwise. */ public abstract boolean onFling(int velocityX, int velocityY); }

方法的注释写的也很清楚,当这个方法被调用并且返回 true 的时候系统就不处理滑动了,而是将滑动交给我们自己处理。所以我们可以监听这个方法,当我们执行快速滑动的时候在这个方法里面计算要滑动的距离并执行 scrollBy(int x, int y) 实现滑动,然后直接返回 true,表示滑动我们自己处理了,不需要系统处理。

处理代码如下:

 //当前滑动距离
 private int offsetY = 0;
 private int offsetX = 0;
 //按下屏幕点
 private int startY = 0;
 private int startX = 0;
 //最后一个可见 view 位置
 private int lastItemPosition = -1;
 //第一个可见view的位置
 private int firstItemPosition = -2;
 //总 itemView 数量
 private int totalNum;
@Override
  public boolean onFling(int velocityX, int velocityY) {
   if (mOrientation == ORIENTATION.NULL) {
    return false;
   }
   //获取开始滚动时所在页面的index
   int page = getStartPageIndex();
   //记录滚动开始和结束的位置
   int endPoint = 0;
   int startPoint = 0;
   //如果是垂直方向
   if (mOrientation == ORIENTATION.VERTICAL) {
    //开始滚动位置,当前开始执行 scrollBy 位置
    startPoint = offsetY;
    if (velocityY <0) {
     page--;
    } else if (velocityY > 0) {
     page++;
    } else if (pageNum != -1) {
     if (lastItemPosition + 1 == totalNum) {
      mRecyclerView.scrollToPosition(0);
     }
     page = pageNum - 1;
    }
    //更具不同的速度判断需要滚动的方向
    //一次滚动一个 mRecyclerView 高度
    endPoint = page * mRecyclerView.getHeight();
   } else {
    startPoint = offsetX;
    if (velocityX <0) {
     page--;
    } else if (velocityX > 0) {
     page++;
    } else if (pageNum != -1) {
     if (lastItemPosition + 1 == totalNum) {
      mRecyclerView.scrollToPosition(0);
     }
     page = pageNum - 1;
    }
    endPoint = page * mRecyclerView.getWidth();
   }
   //使用动画处理滚动
   if (mAnimator == null) {
    mAnimator = ValueAnimator.ofInt(startPoint, endPoint);
    mAnimator.setDuration(300);
    mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
     @Override
     public void onAnimationUpdate(ValueAnimator animation) {
      int nowPoint = (int) animation.getAnimatedValue();
      if (mOrientation == ORIENTATION.VERTICAL) {
       int dy = nowPoint - offsetY;
       if (dy == 0) return;
       //这里通过RecyclerView的scrollBy方法实现滚动。
       mRecyclerView.scrollBy(0, dy);
      } else {
       int dx = nowPoint - offsetX;
       mRecyclerView.scrollBy(dx, 0);
      }
     }
    });
    mAnimator.addListener(new AnimatorListenerAdapter() {
     //动画结束
     @Override
     public void onAnimationEnd(Animator animation) {
      //回调监听
      if (null != mOnPageChangeListener) {
       mOnPageChangeListener.onPageChange(getPageIndex());
      }
      //滚动完成,进行判断是否滚到头了或者滚到尾部了
      RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
      //判断是当前layoutManager是否为LinearLayoutManager
      // 只有LinearLayoutManager才有查找第一个和最后一个可见view位置的方法
      if (layoutManager instanceof LinearLayoutManager) {
       LinearLayoutManager linearManager = (LinearLayoutManager) layoutManager;
       //获取最后一个可见view的位置
       lastItemPosition = linearManager.findLastVisibleItemPosition();
       //获取第一个可见view的位置
       firstItemPosition = linearManager.findFirstVisibleItemPosition();
      }
      totalNum = mRecyclerView.getAdapter().getItemCount();
      if (totalNum == lastItemPosition + 1) {
       updateLayoutManger();
      }
      if (firstItemPosition == 0) {
       updateLayoutManger();
      }
     }
    });
   } else {
    mAnimator.cancel();
    mAnimator.setIntValues(startPoint, endPoint);
   }
   mAnimator.start();
   return true;
  }
 }

再看 OnScrollListener 滚动监听方法:

public abstract static class OnScrollListener {
  /**
  * Callback method to be invoked when RecyclerView's scroll state changes.
  *
  * @param recyclerView The RecyclerView whose scroll state has changed.
  * @param newState  The updated scroll state. One of {@link #SCROLL_STATE_IDLE},
  *      {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}.
  */
  public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
  /**
  * Callback method to be invoked when the RecyclerView has been scrolled. This will be
  * called after the scroll has completed.
  * 

* This callback will also be called if visible item range changes after a layout * calculation. In that case, dx and dy will be 0. * 滚动完成调用 * @param recyclerView The RecyclerView which scrolled. * @param dx The amount of horizontal scroll. * @param dy The amount of vertical scroll. */ public void onScrolled(RecyclerView recyclerView, int dx, int dy){} }

这个监听类主要有两个方法一个是 onScrollStateChanged(RecyclerView recyclerView, int newState) 滚动状态发生变化调用, onScrolled(RecyclerView recyclerView, int dx, int dy) RecyclerView 发生滚动和滚动完成调用。有了这两个监听,当我们进行缓慢滑动我们就可以在 onScrollStateChanged(RecyclerView recyclerView, int newState) 中监听滑动如果结束并且超过一定距离去执行翻页,而通过 onScrolled(RecyclerView recyclerView, int dx, int dy) 方法可以记录当前的滑动距离。

处理方法如下:

@Override
  public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   //如果滑动停止
   if (newState == RecyclerView.SCROLL_STATE_IDLE && mOrientation != ORIENTATION.NULL) {
    boolean move;
    int vX = 0, vY = 0;
    if (mOrientation == ORIENTATION.VERTICAL) {
     int absY = Math.abs(offsetY - startY);
     //如果滑动的距离超过屏幕的一半表示需要滑动到下一页
     move = absY > recyclerView.getHeight() / 2;
     vY = 0;
     if (move) {
      vY = offsetY - startY <0 &#63; -1000 : 1000;
     }
    } else {
     int absX = Math.abs(offsetX - startX);
     move = absX > recyclerView.getWidth() / 2;
     if (move) {
      vX = offsetX - startX <0 &#63; -1000 : 1000;
     }
    }
    //调用滑动
    mOnFlingListener.onFling(vX, vY);
   }
  }
 @Override
  public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
   //滚动结束记录滚动的偏移量
   //记录当前滚动到的位置
   offsetY += dy;
   offsetX += dx;
  }
 }

到这里我们要实现滑动的方法和时机基本就搞定了,剩下的就是滑动位置计算和滑动效果实现,滑动位置计算就是一次滑动一整页,这个没什么可说的,所以简单说下实现弹性滑动效果。

2.4 ValueAnimator 实现弹性滑动效果

我们知道如果我们直接调用 scrollBy(int x, int y) 这个方法去滑动,那么是没有缓慢滑动的效果,看着有点愣,所以这里我们通过 ValueAnimator 这个类来实现缓慢滑动的效果,这个就很简单了,直接贴代码:

if (mAnimator == null) {
    mAnimator = ValueAnimator.ofInt(startPoint, endPoint);
    mAnimator.setDuration(300);
    mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
     @Override
     public void onAnimationUpdate(ValueAnimator animation) {
      int nowPoint = (int) animation.getAnimatedValue();
      if (mOrientation == ORIENTATION.VERTICAL) {
       int dy = nowPoint - offsetY;
       if (dy == 0) return;
       //这里通过RecyclerView的scrollBy方法实现滚动。
       mRecyclerView.scrollBy(0, dy);
      } else {
       int dx = nowPoint - offsetX;
       mRecyclerView.scrollBy(dx, 0);
      }
     }
    });

2.5 翻页至某一页

这里翻页至某一页的实现有了上面的基础就很好实现了,就是直接调用 我们已经实现好了的 onFling(int velocityX, int velocityY) 方法,然后把页数传递过去计算一下就可以了 :

public void setPageNum(int page) {
 this.pageNum = page;
 mOnFlingListener.onFling(0, 0);
}

到这里前面说的功能已经全部实现完毕.

具体代码细节请见:

github 地址: pagerecyclerview

本地下载:点击这里

总结

以上就是这篇文章的全部内容了,希望本文的内容对各位Android开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。


推荐阅读
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 【Windows】实现微信双开或多开的方法及步骤详解
    本文介绍了在Windows系统下实现微信双开或多开的方法,通过安装微信电脑版、复制微信程序启动路径、修改文本文件为bat文件等步骤,实现同时登录两个或多个微信的效果。相比于使用虚拟机的方法,本方法更简单易行,适用于任何电脑,并且不会消耗过多系统资源。详细步骤和原理解释请参考本文内容。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
author-avatar
戴劳力士_484
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有