需求
城市列表快速定位:通过右边的地区字母快速导航到特定位置
通讯录的快速定位
分析思路
网上查看的文章说:分三种情况,即屏幕上方、屏幕中、屏幕下方;分别处理代码逻辑;
实际分析后发现完全可以使用比较简单的方式实现:
默认API
RecyclerView 提供了3种方法用于滑动到特定位置的API
API | 区别 |
---|---|
scrollBy(int x, int y) |
根据x、y轴的距离,滑动 |
smoothScrollToPosition(int position) |
平滑滚动到特定 position |
scrollToPosition(int position) |
滚动到特定position |
但是通过具体代码的实践:均不能达到预期效果
-
scrollBy()
方法可以通计算 child.getTop() 获得其需要滑动的距离,但是如果 child 在屏幕之外,需要的 child 未创建,无法获得 -
scrollToPosition(int position)
、smoothScrollToPosition(int position)
方法可以通过 position 滑动到特定下标,但是有个特点:- position < firstViewItemPosition,则列表向下滚动 目标 position 滚动到顶部位置
- firstVisibleItemPosition < position < lastVisibleItemPosition :列表不会滚动
- position > lastVisibleItemPosition : 列表向上滚动, 目标position 滚动到屏幕底部位置
可见均不能达到我们的预期;
那我们去分析一下 smoothScrollToPosition() 的具体实现,看下我们是否可以干预一下其滚动规则
源码分析
// RecyclerView.java
public void smoothScrollToPosition(int position) {
if (mLayoutFrozen) {
return;
}
if (mLayout == null) {
Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " +
"Call setLayoutManager with a non-null argument.");
return;
}
// @VisibleForTesting LayoutManager mLayout;
// 这里调用了 LayouManager 去实现滚动。
mLayout.smoothScrollToPosition(this, mState, position);
}
// LayoutManager 类
public void smoothScrollToPosition(RecyclerView recyclerView, State state, int position) {
// LayoutManager 中并没有实现代码,而是由 我们设置的 其子类来具体的实现的
Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling");
}
// LinearLayoutManager 类
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
// 滚动控制器
LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext());
// 设置滚动目标 position
linearSmoothScroller.setTargetPosition(position);
// 由 LineraManager 调用
startSmoothScroll(linearSmoothScroller);
}
// LayoutManager 类
public void startSmoothScroll(SmoothScroller smoothScroller) {
// 数据以及状态的校验
if (mSmoothScroller != null && smoothScroller != mSmoothScroller && mSmoothScroller.isRunning()) {
mSmoothScroller.stop();
}
mSmoothScroller = smoothScroller;
// 触发滚动
mSmoothScroller.start(mRecyclerView, this);
}
到这里,我们可以发现,RecyclerView 的滚动触发是由 LayoutManager 调用,其控制器的定义是由具体的 Manager 来设置;
继续来分析 SmoothScroller 的源码
// 抽象类
public static abstract class SmoothScroller{}
/**
* 线性平滑滚动控制器。
*/
public class LinearSmoothScroller extends RecyclerView.SmoothScroller {
// 其他源码 ...
/**
* 当滚动到子视图时,这种方法定义了:child 与 parent 是否应该左对齐或右对齐。
*
* When scrolling towards a child view, this method defines whether we should align the left
* or the right edge of the child with the parent RecyclerView.
*
* @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY;
*/
protected int getHorizontalSnapPreference() {
return mTargetVector == null || mTargetVector.x == 0 ? SNAP_TO_ANY :
mTargetVector.x > 0 ? SNAP_TO_END : SNAP_TO_START;
}
/**
* 当滚动到子视图时,这种方法定义了:child 与 parent 是否应该顶对齐或底对齐。
*
* When scrolling towards a child view, this method defines whether we should align the top
* or the bottom edge of the child with the parent RecyclerView.
*
* @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY;
*/
protected int getVerticalSnapPreference() {
return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
}
// 其他源码 ...
}
通过注释就可以看出:上面两个方法控制了 childView 的对齐方式,且是 protected 修饰的方法,那我们完全可以通过自定义返回对齐方式
具体实现
// 定义滚动控制器
private LinearSmoothScroller smoothScroller;
smoothScroller= new LinearSmoothScroller(inflater.getContext()) {
// 这里考虑的是垂直列表
@Override
protected int getVerticalSnapPreference() {
// 固定返回顶对齐方式
return SNAP_TO_START;
}
};
// 设置滚动目标 position
smoothScroller.setTargetPosition(index);
// 主动触发滚动
recyclerView.getLayoutManager().startSmoothScroll(smoothScroller);
至此,即可实现文头的需求方案;
网友评论