@whosea
2018-03-13T10:43:24.000000Z
字数 5741
阅读 6631
Android
原文链接:https://www.zybuluo.com/whosea/note/1006457
compile 'com.github.whosea:GravitySnapRecycleView:1.0.0'
项目应用效果:


demo效果:



创建处理翻页效果类(另外总共支持start end top bottom 和 center 五种行为):
GravitySnapHelper snapHelper = new GravitySnapHelper(Gravity.CENTER);snapHelper.setColumn(3);//如果一页里面有超过1列的都需要设置snapHelper.setCanPageScroll(true);//是否启用一页一页的滚动,默认不启用snapHelper.attachToRecyclerView(recyclerview);
翻页监听
PageIndicatorHelper pageIndicatorHelper = new PageIndicatorHelper();pageIndicatorHelper.setPageColumn(column);pageIndicatorHelper.setRecyclerView(rvCenter);pageIndicatorHelper.setOnPageChangeListener(new GravityPageChangeListener() {@Overridepublic void onPageSelected(int position,int currentPage,int totalPage) {Log.e("MainActivity",currentPage+ "/"+totalPage);}@Overridepublic void onPageScrollStateChanged(int state) {}});
在很久很久之前,在某一个城市里面,产品突然想实现下图的效果:

一开始的想法是:使用ViewPager+Fragment+PageIndicator实现,但是发现item上面的勾选按钮是单选的,当前页面选中后,需要把其他页面勾选过的取消,这样做就需要做监听或者EventBus的方式通知来实现,这样做代码肯定复杂凌乱,而且需要为该方法创建个Fragment来处理。显然这个方案只能作为备选方案。
后期新方案:使用Recycleview+SnapHelper+PageIndicator实现,这套方案完全避开了创建Fragment以及更新勾选的问题,只需一个Recycleview既可实现。虽然Google提供的PagerSnapHelper和LinearSnapHelper,但是美中不足的LinearSnapHelper不能实现一页一页的滑动,是PagerSnapHelper是Item 居中对齐的滑动。但该问题与备选方案相比还是好了很多,只需解决一些细节的问题既可。
1.新增水平翻页滑动
2.PageIndicator与Recycleview配合
3.数据排序问题
前提:该方案是基于GridPagerSnapHelper修改而成。
主要在GravityDelegate类里面新增水平翻页的处理。
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {if (recyclerView != null) {recyclerView.setOnFlingListener(null);if (gravity == Gravity.START || gravity == Gravity.END|| gravity == Gravity.CENTER) {isRtlHorizontal = isRtl();}if (listener != null) {recyclerView.addOnScrollListener(mScrollListener);}}}
新增findCenterView和distanceToCenter两个方法处理翻页,findCenterView主要是返回当前Recycleview距离其中间最近的view,该view会作为目标view最终给distanceToCenter处理,计算出该view最终处于哪一页,算出当前位置距离当前页开始位置有多远,返回最终要滑动的距离。
/*** 返回距离Recycleview中间位置最近的view* @param layoutManager* @return*/private View findCenterView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {...int absClosest = Integer.MAX_VALUE;for (int i = 0; i < childCount; i++) {View child = layoutManager.getChildAt(i);int childCenter = 0;if(isRtlHorizontal){childCenter = (helper.getTotalSpace() - helper.getDecoratedEnd(child))+ (helper.getDecoratedMeasurement(child) / 2);}else{childCenter = helper.getDecoratedStart(child)+ (helper.getDecoratedMeasurement(child) / 2);}int absDistance = Math.abs(childCenter - center);/** 找出离中心最近的view **/if (absDistance < absClosest) {absClosest = absDistance;closestChild = child;}}...}
private int distanceToCenter(RecyclerView.LayoutManager layoutManager, View targetView, OrientationHelper helper) {int columnWidth = helper.getTotalSpace() / column;int position = layoutManager.getPosition(targetView);//当前第一个可见view所在的页int pageIndex = 0;int row = 1;//当前页第一个view的位置int currentPagePosition = 0;if (layoutManager instanceof GridLayoutManager) {row = ((GridLayoutManager) layoutManager).getSpanCount();}//该位置在哪一页pageIndex = pageIndex(position,row);//当前页开始的位置currentPagePosition = pageIndex * countOfPage(row);//算出当前位置距离开始位置有多远int distance = ((position - currentPagePosition) / row) * columnWidth;int distanceToCenter = 0;int childStart = 0;if(isRtlHorizontal){childStart = helper.getDecoratedEnd(targetView);distanceToCenter = childStart - (helper.getTotalSpace() - distance);}else{childStart = helper.getDecoratedStart(targetView);distanceToCenter = childStart - distance;}return distanceToCenter;}
新增PageIndicatorHelper辅助类,监听用户滑动到某一位置,计算出页面位置,最后回调。核心代码如下:
private OnScrollListener scrollListener = new OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);onPageScrollStateChanged(newState);if (newState == RecyclerView.SCROLL_STATE_IDLE) {RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();int page = 0;int position = 0;int secondPosition = 0;/*** 如果只用findFirstCompletelyVisibleItemPosition会有个小问题,用户拖到一半松开时候* 会有一瞬间的停顿,而系统会判定这个是空闲状态(SCROLL_STATE_IDLE),那么就会返回NO_POSITION(-1)的位置* 因此需要结合findFirstVisibleItemPosition一起判断*/if (layoutManager instanceof GridLayoutManager) {GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;position = gridLayoutManager.findFirstCompletelyVisibleItemPosition();secondPosition = gridLayoutManager.findFirstVisibleItemPosition();if(position == RecyclerView.NO_POSITION && secondPosition > 0){position = secondPosition;}page = getCurrentPage(position);} else if (layoutManager instanceof LinearLayoutManager) {LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;position = linearLayoutManager.findFirstCompletelyVisibleItemPosition();secondPosition = linearLayoutManager.findFirstVisibleItemPosition();if(position == RecyclerView.NO_POSITION && secondPosition > 0){position = secondPosition;}page = position;}onPageSelected(position,page);}}@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {super.onScrolled(recyclerView, dx, dy);}};
正常情况下,GridLayoutManager的水平布局位置如下:
但是我们要的效果是如下:
因此可以使用InvertRowColumnDataTransform对数据进行位置互换,类似倒置矩阵。
public List<T> transform(List<T> dataList) {List<T> destList = new ArrayList<T>();//页数int pageSize = row * column;//总数量int size = dataList.size();//总换后的总数量,包括一页空的数据int afterTransformSize;if (size < pageSize) {afterTransformSize = pageSize;} else if (size % pageSize == 0) {afterTransformSize = size;} else {afterTransformSize = (size / pageSize + 1) * pageSize;}//开始遍历位置,类似置换矩阵for (int i = 0; i < afterTransformSize; i++) {//第几页int pageIndex = i / pageSize;//为横坐标int columnIndex = (i - pageSize * pageIndex) / row;//为纵坐标int rowIndex = (i - pageSize * pageIndex) % row;//int result = (rowIndex * column + columnIndex) + pageIndex * pageSize;if (result >= 0 && result < size) {destList.add(dataList.get(result));} else {destList.add(null);}}return destList;}
献上源码一枚,各位看官轻拍:
源码
未实现功能:
1.自动轮播