[关闭]
@whosea 2018-03-13T10:43:24.000000Z 字数 5741 阅读 6332

Android Recycleview 翻页效果(类似ViewPager)



Android

原文链接:https://www.zybuluo.com/whosea/note/1006457

添加依赖

compile 'com.github.whosea:GravitySnapRecycleView:1.0.0'

效果

项目应用效果:

demo效果:

用法

创建处理翻页效果类(另外总共支持start end top bottom 和 center 五种行为):

  1. GravitySnapHelper snapHelper = new GravitySnapHelper(Gravity.CENTER);
  2. snapHelper.setColumn(3);//如果一页里面有超过1列的都需要设置
  3. snapHelper.setCanPageScroll(true);//是否启用一页一页的滚动,默认不启用
  4. snapHelper.attachToRecyclerView(recyclerview);

翻页监听

  1. PageIndicatorHelper pageIndicatorHelper = new PageIndicatorHelper();
  2. pageIndicatorHelper.setPageColumn(column);
  3. pageIndicatorHelper.setRecyclerView(rvCenter);
  4. pageIndicatorHelper.setOnPageChangeListener(new GravityPageChangeListener() {
  5. @Override
  6. public void onPageSelected(int position,int currentPage,int totalPage) {
  7. Log.e("MainActivity",currentPage+ "/"+totalPage);
  8. }
  9. @Override
  10. public void onPageScrollStateChanged(int state) {
  11. }
  12. });

起因

在很久很久之前,在某一个城市里面,产品突然想实现下图的效果:

一开始的想法是:使用ViewPager+Fragment+PageIndicator实现,但是发现item上面的勾选按钮是单选的,当前页面选中后,需要把其他页面勾选过的取消,这样做就需要做监听或者EventBus的方式通知来实现,这样做代码肯定复杂凌乱,而且需要为该方法创建个Fragment来处理。显然这个方案只能作为备选方案。
后期新方案:使用Recycleview+SnapHelper+PageIndicator实现,这套方案完全避开了创建Fragment以及更新勾选的问题,只需一个Recycleview既可实现。虽然Google提供的PagerSnapHelper和LinearSnapHelper,但是美中不足的LinearSnapHelper不能实现一页一页的滑动,是PagerSnapHelper是Item 居中对齐的滑动。但该问题与备选方案相比还是好了很多,只需解决一些细节的问题既可。

新方案需要解决的问题

1.新增水平翻页滑动
2.PageIndicator与Recycleview配合
3.数据排序问题

前提:该方案是基于GridPagerSnapHelper修改而成。

新增水平翻页滑动

主要在GravityDelegate类里面新增水平翻页的处理。

  1. public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
  2. if (recyclerView != null) {
  3. recyclerView.setOnFlingListener(null);
  4. if (gravity == Gravity.START || gravity == Gravity.END
  5. || gravity == Gravity.CENTER) {
  6. isRtlHorizontal = isRtl();
  7. }
  8. if (listener != null) {
  9. recyclerView.addOnScrollListener(mScrollListener);
  10. }
  11. }
  12. }

新增findCenterViewdistanceToCenter两个方法处理翻页,findCenterView主要是返回当前Recycleview距离其中间最近的view,该view会作为目标view最终给distanceToCenter处理,计算出该view最终处于哪一页,算出当前位置距离当前页开始位置有多远,返回最终要滑动的距离。

  1. /**
  2. * 返回距离Recycleview中间位置最近的view
  3. * @param layoutManager
  4. * @return
  5. */
  6. private View findCenterView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {
  7. ...
  8. int absClosest = Integer.MAX_VALUE;
  9. for (int i = 0; i < childCount; i++) {
  10. View child = layoutManager.getChildAt(i);
  11. int childCenter = 0;
  12. if(isRtlHorizontal){
  13. childCenter = (helper.getTotalSpace() - helper.getDecoratedEnd(child))
  14. + (helper.getDecoratedMeasurement(child) / 2);
  15. }else{
  16. childCenter = helper.getDecoratedStart(child)
  17. + (helper.getDecoratedMeasurement(child) / 2);
  18. }
  19. int absDistance = Math.abs(childCenter - center);
  20. /** 找出离中心最近的view **/
  21. if (absDistance < absClosest) {
  22. absClosest = absDistance;
  23. closestChild = child;
  24. }
  25. }
  26. ...
  27. }
  1. private int distanceToCenter(RecyclerView.LayoutManager layoutManager, View targetView, OrientationHelper helper) {
  2. int columnWidth = helper.getTotalSpace() / column;
  3. int position = layoutManager.getPosition(targetView);
  4. //当前第一个可见view所在的页
  5. int pageIndex = 0;
  6. int row = 1;
  7. //当前页第一个view的位置
  8. int currentPagePosition = 0;
  9. if (layoutManager instanceof GridLayoutManager) {
  10. row = ((GridLayoutManager) layoutManager).getSpanCount();
  11. }
  12. //该位置在哪一页
  13. pageIndex = pageIndex(position,row);
  14. //当前页开始的位置
  15. currentPagePosition = pageIndex * countOfPage(row);
  16. //算出当前位置距离开始位置有多远
  17. int distance = ((position - currentPagePosition) / row) * columnWidth;
  18. int distanceToCenter = 0;
  19. int childStart = 0;
  20. if(isRtlHorizontal){
  21. childStart = helper.getDecoratedEnd(targetView);
  22. distanceToCenter = childStart - (helper.getTotalSpace() - distance);
  23. }else{
  24. childStart = helper.getDecoratedStart(targetView);
  25. distanceToCenter = childStart - distance;
  26. }
  27. return distanceToCenter;
  28. }

PageIndicator与Recycleview配合

新增PageIndicatorHelper辅助类,监听用户滑动到某一位置,计算出页面位置,最后回调。核心代码如下:

  1. private OnScrollListener scrollListener = new OnScrollListener() {
  2. @Override
  3. public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
  4. super.onScrollStateChanged(recyclerView, newState);
  5. onPageScrollStateChanged(newState);
  6. if (newState == RecyclerView.SCROLL_STATE_IDLE) {
  7. RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
  8. int page = 0;
  9. int position = 0;
  10. int secondPosition = 0;
  11. /**
  12. * 如果只用findFirstCompletelyVisibleItemPosition会有个小问题,用户拖到一半松开时候
  13. * 会有一瞬间的停顿,而系统会判定这个是空闲状态(SCROLL_STATE_IDLE),那么就会返回NO_POSITION(-1)的位置
  14. * 因此需要结合findFirstVisibleItemPosition一起判断
  15. */
  16. if (layoutManager instanceof GridLayoutManager) {
  17. GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
  18. position = gridLayoutManager.findFirstCompletelyVisibleItemPosition();
  19. secondPosition = gridLayoutManager.findFirstVisibleItemPosition();
  20. if(position == RecyclerView.NO_POSITION && secondPosition > 0){
  21. position = secondPosition;
  22. }
  23. page = getCurrentPage(position);
  24. } else if (layoutManager instanceof LinearLayoutManager) {
  25. LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
  26. position = linearLayoutManager.findFirstCompletelyVisibleItemPosition();
  27. secondPosition = linearLayoutManager.findFirstVisibleItemPosition();
  28. if(position == RecyclerView.NO_POSITION && secondPosition > 0){
  29. position = secondPosition;
  30. }
  31. page = position;
  32. }
  33. onPageSelected(position,page);
  34. }
  35. }
  36. @Override
  37. public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
  38. super.onScrolled(recyclerView, dx, dy);
  39. }
  40. };

数据排序问题

正常情况下,GridLayoutManager的水平布局位置如下:

但是我们要的效果是如下:

因此可以使用InvertRowColumnDataTransform对数据进行位置互换,类似倒置矩阵。

  1. public List<T> transform(List<T> dataList) {
  2. List<T> destList = new ArrayList<T>();
  3. //页数
  4. int pageSize = row * column;
  5. //总数量
  6. int size = dataList.size();
  7. //总换后的总数量,包括一页空的数据
  8. int afterTransformSize;
  9. if (size < pageSize) {
  10. afterTransformSize = pageSize;
  11. } else if (size % pageSize == 0) {
  12. afterTransformSize = size;
  13. } else {
  14. afterTransformSize = (size / pageSize + 1) * pageSize;
  15. }
  16. //开始遍历位置,类似置换矩阵
  17. for (int i = 0; i < afterTransformSize; i++) {
  18. //第几页
  19. int pageIndex = i / pageSize;
  20. //为横坐标
  21. int columnIndex = (i - pageSize * pageIndex) / row;
  22. //为纵坐标
  23. int rowIndex = (i - pageSize * pageIndex) % row;
  24. //
  25. int result = (rowIndex * column + columnIndex) + pageIndex * pageSize;
  26. if (result >= 0 && result < size) {
  27. destList.add(dataList.get(result));
  28. } else {
  29. destList.add(null);
  30. }
  31. }
  32. return destList;
  33. }

献上源码一枚,各位看官轻拍:
源码

未实现功能:
1.自动轮播

参考文章

GridPagerSnapHelper

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注