RecycleView滑动,顶部tab跟着变化位置,类似于淘宝详情页,效果是这样的
1.实现思路
简单讲就是监听RecycleView的滑动,根据滑动调整tab位置。相应的监听tab点击事件,滑动recycleView到相应位置。
2.滑动RecycleView调整tab位置
2.1获得滑动位置
LayoutManager提供了获得首个可见item的方法
int position = layoutManager.findFirstVisibleItemPosition();
2.2将滑动位置转换为类型位置
这里我item类型有普通类型和大类的标题类型,通过instanceof判断类型是否是我所属的大类的标题类型,然后根据大类标题类型到顶部的距离判断,再根据类型名称进行定位。
然后通过getTop获得到顶部的距离,根据距离进行分类间的跳转。
int top = mRecyclerView.getChildAt(0).getTop();
2.3完整代码
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//不捕获自动滚动,非用户触摸产生的滑动
if (isFormTabScroll) {
return;
}
//监听滑动距离,改变tab选中位置
int position = layoutManager.findFirstVisibleItemPosition();
int realPosition = position - mAdapter.getHeaderSize();
//因为有1个header所以>=-1
if (realPosition >= -1 && realPosition < mAdapter.getList().size()) {
//通过判断分类卡片位置定位tab
if (mealCardIndxList.size() == 0) {//记录大类的流量卡片位置
for (int i = 0; i < mAdapter.getList().size(); i++) {
if (mAdapter.getList().get(i) instanceof CardFlowMeal) {
mealCardIndxList.add(i);
}
}
}
if (mealCardIndxList.size() + 1 == indicator.getTitles().size()) {//防止下标越界异常
//判断当前item所处类定位tab
for (int i = 0; i < mealCardIndxList.size(); i++) {
if (i == 0) {
if (realPosition >= -1 && realPosition < mealCardIndxList.get(i)) {
if (indicator.getCurrentPosition() != 0) {
indicator.setCurrentItem(0);
break;
}
}
} else if (realPosition >= mealCardIndxList.get(i - 1) && realPosition < mealCardIndxList.get(i)) {
if (indicator.getCurrentPosition() != i) {
indicator.setCurrentItem(i);
break;
}
}
//可能会有i==0的情况下属于如下情况 所以不加else if
if (i == mealCardIndxList.size() - 1) {
if (realPosition >= mealCardIndxList.get(i)) {
if (indicator.getCurrentPosition() != i + 1) {
indicator.setCurrentItem(i + 1);
}
}
}
}
}
}
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
switch (newState) {
case RecyclerView.SCROLL_STATE_DRAGGING:
isFormTabScroll = false;
break;
case RecyclerView.SCROLL_STATE_IDLE:
if (flowDetailActivity != null && !flowDetailActivity.isCloseNps()) {
UIUtils.postDelayRunnable(new Runnable() {
@Override
public void run() {
if (!flowDetailActivity.isShowNps()) {
flowDetailActivity.showNPS();
}
}
}, 100);
}
break;
default:
break;
}
}
});
3.点击tab位置RecycleView滑动到相应位置
通过RecycleView的scrollBy()
和scrollToPosition()
两个方法实现。为scrollToPosition()
只会保证滑动的position出现在视野中,不会保证该position在顶端,所以需要通过scrollBy()
完成置顶的滑动。
public void moveRecycleViewToPosition(int n) {
isFormTabScroll = true;
//先从RecyclerView的LayoutManager中获取第一项和最后一项的Position
int firstItem = layoutManager.findFirstVisibleItemPosition();
int lastItem = layoutManager.findLastVisibleItemPosition();
//然后区分情况
if (n < firstItem) {
//当要置顶的项在当前显示的第一个项的前面时
mRecyclerView.scrollToPosition(n);
if (n != 0) {
mIndex = n;
needMoveToTop = true;
}
} else if (n <= lastItem) {
//当要置顶的项已经在屏幕上显示时
int top = mRecyclerView.getChildAt(n - firstItem).getTop();
mRecyclerView.scrollBy(0, top);
} else {
//当要置顶的项在当前显示的最后一项的后面时
mRecyclerView.scrollToPosition(n);
//这里这个变量是用在RecyclerView滚动监听里面的
mIndex = n;
needMoveToTop = true;
}
}
可以看到在该position不在屏幕中时,指定了一个字段needMoveToTop = true
,因为需要在scrollToPosition()
方法后再执行scrollBy()
完成置顶操作。这个标记是监听RecycleView滑动完成之后用来标识完成scrollBy()
最后置顶滑动的,具体代码如下:
mRecyclerView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
//将改item滑动到顶部
if (needMoveToTop) {
needMoveToTop = false;
//获取要置顶的项在当前屏幕的位置,mIndex是记录的要置顶项在RecyclerView中的位置
int n = mIndex - layoutManager.findFirstVisibleItemPosition();
if (0 <= n && n < mRecyclerView.getChildCount()) {
//获取要置顶的项顶部离RecyclerView顶部的距离
int top = mRecyclerView.getChildAt(n).getTop();
//最后的移动
mRecyclerView.scrollBy(0, top);
}
}
}
});
总体实现难度并不大,至此就完成了滑动锚点定位的逻辑。