RecyclerView - 实现目标Item滚动到指定位置(SmoothScroller)
如果需要实现RecyclerView滚动到指定目标的位置,简单的说明下:
#1. RecyclerView.scrollToPosition(int)
/**
* Convenience method to scroll to a certain position.
*
* RecyclerView does not implement scrolling logic, rather forwards the call to
* {@link RecyclerView.LayoutManager#scrollToPosition(int)}
* @param position Scroll to this adapter position
* @see RecyclerView.LayoutManager#scrollToPosition(int)
*/
public void scrollToPosition(int position) {
if (mLayoutSuppressed) {
return;
}
stopScroll();
if (mLayout == null) {
Log.e(TAG, "Cannot scroll to position a LayoutManager set. "
+ "Call setLayoutManager with a non-null argument.");
return;
}
mLayout.scrollToPosition(position);
awakenScrollBars();
}
注解:滚动到某个位置的便捷方法。 RecyclerView不实现滚动逻辑,而是将调用转发到RecyclerView.LayoutManager.scrollToPosition(int),所以我们也可以理解为LayoutManager.scrollToPosition(int)
通过这个方法,我们可以使目标Item滚动到当前可视位置,而它不会将目标Item刻意的滚动到顶部第一个可见位置,或者底部的第后一个可见位置,这里只会将目标Item可见,我们也看下代码:
#LayoutManager.scrollToPosition(int)
/**
* <p>Scroll the RecyclerView to make the position visible.</p>
*
* <p>RecyclerView will scroll the minimum amount that is necessary to make the
* target position visible. If you are looking for a similar behavior to
* {@link android.widget.ListView#setSelection(int)} or
* {@link android.widget.ListView#setSelectionFromTop(int, int)}, use
* {@link #scrollToPositionWithOffset(int, int)}.</p>
*
* <p>Note that scroll position change will not be reflected until the next layout call.</p>
*
* @param position Scroll to this adapter position
* @see #scrollToPositionWithOffset(int, int)
*/
@Override
public void scrollToPosition(int position) {
mPendingScrollPosition = position;
mPendingScrollPositionOffset = INVALID_OFFSET;
if (mPendingSavedState != null) {
mPendingSavedState.invalidateAnchor();
}
requestLayout();
}
* Scroll the RecyclerView to make the position visible. => 滚动RecyclerView以使该位置可见。
#2. RecyclerView/LayoutManager.scrollToPositionWithOffset(int, offset)
/**
* Scroll to the specified adapter position with the given offset from resolved layout
* start. Resolved layout start depends on {@link #getReverseLayout()},
* {@link ViewCompat#getLayoutDirection(android.view.View)} and {@link #getStackFromEnd()}.
* <p>
* For example, if layout is {@link #VERTICAL} and {@link #getStackFromEnd()} is true, calling
* <code>scrollToPositionWithOffset(10, 20)</code> will layout such that
* <code>item[10]</code>'s bottom is 20 pixels above the RecyclerView's bottom.
* <p>
* Note that scroll position change will not be reflected until the next layout call.
* <p>
* If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
*
* @param position Index (starting at 0) of the reference item.
* @param offset The distance (in pixels) between the start edge of the item view and
* start edge of the RecyclerView.
* @see #setReverseLayout(boolean)
* @see #scrollToPosition(int)
*/
public void scrollToPositionWithOffset(int position, int offset) {
mPendingScrollPosition = position;
mPendingScrollPositionOffset = offset;
if (mPendingSavedState != null) {
mPendingSavedState.invalidateAnchor();
}
requestLayout();
}
简单的说下:offset - 项目视图的起始边缘与RecyclerView的起始边缘之间的距离(以像素为单位)。这里相比scrollToPosition,我们就可以设置偏移量:
如果offset = 0,我们可以理解为将目标Item刻意的滚动到顶部第一个可见位置,如果offset = 100,将目标Item刻意的滚动到距离顶部第一个可见位置往下偏移100px,然后以此类推...
如果只是想使某个位置可见,请使用scrollToPosition(int)
#3. RecyclerView.smoothScrollBy(dx, dy)
/**
* Animate a scroll by the given amount of pixels along either axis.
*
* @param dx Pixels to scroll horizontally
* @param dy Pixels to scroll vertically
*/
public void smoothScrollBy(@Px int dx, @Px int dy) {
smoothScrollBy(dx, dy, null);
}
这里smoothScrollBy是有一系列的方法,有丰富的参数来展示不同的滚动效果:
@Px int dx, // dx像素水平滚动
@Px int dy, // dy像素垂直滚动
@Nullable Interpolator interpolator, // 用于滚动的插值器。 如果为null,则RecyclerView将使用内部默认插值器。
int duration, // 动画的持续时间(以毫秒为单位)。 设置为UNDEFINED_DURATION可以根据内部定义的标准初始速度自动计算持续时间。 小于1的持续时间(不等于UNDEFINED_DURATION)将导致对scrollBy(int,int)的调用。
boolean withNestedScrolling // 为True时执行嵌套滚动的平滑滚动。 如果持续时间小于0且不等于UNDEFINED_DURATION,则不会发生平滑滚动,因此不会发生嵌套滚动。
我们可以将目标Item距离目标位置的像素计算出来(dx或dy),然后通过这个方法,实现滚动到目标位置。
#4. RecyclerView.smoothScrollToPosition(int)
因为ListView有smoothScrollToPosition方法 ,所以RecyclerView也应该有,但是调用该方法却发现不起作用,然后看了源码,如下:
/**
* Starts a smooth scroll to an adapter position.
* <p>
* To support smooth scrolling, you must override
* {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a
* {@link SmoothScroller}.
* <p>
* {@link LayoutManager} is responsible for creating the actual scroll action. If you want to
* provide a custom smooth scroll logic, override
* {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your
* LayoutManager.
*
* @param position The adapter position to scroll to
* @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int)
*/
public void smoothScrollToPosition(int position) {
if (mLayoutSuppressed) {
return;
}
if (mLayout == null) {
Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
+ "Call setLayoutManager with a non-null argument.");
return;
}
mLayout.smoothScrollToPosition(this, mState, position);
}
/**
* <p>Smooth scroll to the specified adapter position.</p>
* <p>To support smooth scrolling, override this method, create your {@link SmoothScroller}
* instance and call {@link #startSmoothScroll(SmoothScroller)}.
* </p>
* @param recyclerView The RecyclerView to which this layout manager is attached
* @param state Current State of RecyclerView
* @param position Scroll to this adapter position.
*/
public void smoothScrollToPosition(RecyclerView recyclerView, State state,
int position) {
Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling");
}
Android 它在RecyclerView中并没有给出这个方法的实现,必须重写smoothScrollToPosition以支持平滑滚动,没错,需要自己动手重写实现平滑滚动!!!
如果需要用这个方法,只能自己去重写RecyclerView的smoothScrollToPosition方法,但是,我在紧挨着该源码的下面看到了这个方法:RecyclerView.SmoothScroller
public void startSmoothScroll(SmoothScroller smoothScroller) {
if (mSmoothScroller != null && smoothScroller != mSmoothScroller
&& mSmoothScroller.isRunning()) {
mSmoothScroller.stop();
}
mSmoothScroller = smoothScroller;
mSmoothScroller.start(mRecyclerView, this);
}
也是这里想要介绍的!
### RecyclerView.SmoothScroller
Base class for smooth scrolling. Handles basic tracking of the target view position and provides methods to trigger a programmatic scroll.
An instance of SmoothScroller is only intended to be used once. You should create a new instance for each call to RecyclerView.LayoutManager.startSmoothScroll(SmoothScroller)
.
大概介绍:用于平滑滚动的基类。 处理目标视图位置的基本跟踪,并提供触发程序化滚动的方法。 SmoothScroller的一个实例只能使用一次。 您应该为每次调用RecyclerView.LayoutManager.startSmoothScroll(SmoothScroller)创建一个新实例。
* 以下代码都是以垂直方向滑动列表做示例:
/**
* Starts a smooth scroll using the provided {@link SmoothScroller}.
*
* <p>Each instance of SmoothScroller is intended to only be used once. Provide a new
* SmoothScroller instance each time this method is called.
*
* <p>Calling this method will cancel any previous smooth scroll request.
*
* @param smoothScroller Instance which defines how smooth scroll should be animated
*/
public void startSmoothScroll(SmoothScroller smoothScroller) {
if (mSmoothScroller != null && smoothScroller != mSmoothScroller
&& mSmoothScroller.isRunning()) {
mSmoothScroller.stop();
}
mSmoothScroller = smoothScroller;
mSmoothScroller.start(mRecyclerView, this);
}
⚠️ 根据上面的源码就可以看出 SmoothScroller的一个实例只能使用一次!!!
使用起来很简单,两三行代码:
private fun smoothMoveToPosition(position: Int) {
val smoothScroller = LinearSmoothScroller(context)
smoothScroller.targetPosition = position
layoutManager.startSmoothScroll(smoothScroller)
}
这里多出来一个类 - LinearSmoothScroller,大概意思:
- 由RecyclerView.SmoothScroller实现,该实现使用LinearInterpolator直到目标位置成为RecyclerView的子级,然后使用DecelerateInterpolator缓慢地接近目标位置。
- 如果您使用的RecyclerView.LayoutManager没有实现RecyclerView.SmoothScroller.ScrollVectorProvider接口,那么您必须重写computeScrollVectorForPosition(int)方法。与支持库捆绑在一起的所有LayoutManager都实现此接口。
#1. 关于对齐方式的设置:
// 将子视图的左侧或顶部与父视图的左侧或顶部对齐
public static final int SNAP_TO_START = -1;
// 将子视图的右侧或底部与父视图的右侧或底部对齐
public static final int SNAP_TO_END = 1;
// 根据当前相对于父级的位置,决定从子级开始还是结束子级。例如,如果视图实际上位于RecyclerView的左侧,则使用SNAP_TO_ANY与使用SNAP_TO_START相同
public static final int SNAP_TO_ANY = 0;
根据源码中的对齐方式的实现:
/**
* 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; depending on the current target vector
* @see #SNAP_TO_START
* @see #SNAP_TO_END
* @see #SNAP_TO_ANY
*/
protected int getVerticalSnapPreference() {
return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
}
所以我们可以尝试去重写该方法: getVerticalSnapPreference()
private fun smoothMoveToPosition(position: Int) {
val smoothScroller = object : LinearSmoothScroller(context) {
override fun getVerticalSnapPreference(): Int {
return SNAP_TO_START
}
}
smoothScroller.targetPosition = position
transactionLayoutManager.startSmoothScroll(smoothScroller)
}
以上的三种对齐方式,可能不足以满足其他的位置,所以想到了能不能设置相对于对齐方式后再偏移一些距离呢?
#2. 设置偏移量:
根据源码,由对齐方式然后再到计算滑动距离的实现:
/**
* Helper method for {@link #calculateDxToMakeVisible(android.view.View, int)} and
* {@link #calculateDyToMakeVisible(android.view.View, int)}
*/
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
snapPreference) {
switch (snapPreference) {
case SNAP_TO_START:
return boxStart - viewStart;
case SNAP_TO_END:
return boxEnd - viewEnd;
case SNAP_TO_ANY:
final int dtStart = boxStart - viewStart;
if (dtStart > 0) {
return dtStart;
}
final int dtEnd = boxEnd - viewEnd;
if (dtEnd < 0) {
return dtEnd;
}
break;
default:
throw new IllegalArgumentException("snap preference should be one of the"
+ " constants defined in SmoothScroller, starting with SNAP_");
}
return 0;
}
看到如果设置的是SNAP_TO_START,我们可以更改 boxStart - viewStart 的值,所以我们可以尝试去重写该方法: calculateDtToFit()
private fun setSmoothScrollerToRequired(position: Int, offset: Int) =
object : LinearSmoothScroller(context) {
override fun getVerticalSnapPreference(): Int {
return SNAP_TO_START
}
override fun calculateDtToFit(
viewStart: Int,
viewEnd: Int,
boxStart: Int,
boxEnd: Int,
snapPreference: Int
): Int {
return if (snapPreference == SNAP_TO_START
&& !countInventoryDecorator.isFirstOfGroup(position)
) {
boxStart - viewStart + offset
} else {
super.calculateDtToFit(viewStart, viewEnd, boxStart, boxEnd, snapPreference)
}
}
}.apply { targetPosition = position }
再调用方法实现目标Item滚动到指定位置:
layoutManager.startSmoothScroll(setSmoothScrollerToRequired(it, 150))
### LinearSmoothScroller还有其他的方式可以重写实现更多的需求,所以这里只是为了 实现目标Item滚动到指定位置所阐述一些理解。
补充:重写smoothScrollToPosition以支持平滑滚动,这里再重写的方法里使用RecyclerView.SmoothScroller也很nice!!
@Override
public void smoothScrollToPosition(RecyclerView recyclerView,
RecyclerView.State state,
int position) {
RecyclerView.SmoothScroller smoothScroller =
new LinearSmoothScroller(recyclerView.getContext()) {
@Override
protected int getVerticalSnapPreference() {
return SNAP_TO_START; // override base class behavior
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
本文地址:https://blog.csdn.net/qq_20613731/article/details/112854243
上一篇: 再说rocketmq消息存储[转]