@whosea
2018-03-13T10:43:24.000000Z
字数 5741
阅读 6332
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() {
@Override
public void onPageSelected(int position,int currentPage,int totalPage) {
Log.e("MainActivity",currentPage+ "/"+totalPage);
}
@Override
public 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() {
@Override
public 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);
}
}
@Override
public 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.自动轮播