我希望适配器更新后,带有LinearLayoutManager的RecyclerView出现在特定项目的滚动位置。 (不是第一个/最后一个位置) 表示首先(重新)布局,此给定位置应位于可见区域。 它的布局不应在位置0的顶部,然后滚动到目标位置。
我的适配器以itemCount = 0开头,将其数据加载到线程中,然后通知其实际计数。但是起始位置必须在count仍为0时已经设置好!
到目前为止,我使用了某种post Runnable
包含scrollToPosition
的方法,但这有副作用(从第一个位置开始,立即跳转到目标位置(0->目标),并且似乎工作不正常与DiffUtil(0-> target-> 0))
编辑:要澄清:我需要使用layoutManager.setStackFromEnd(true);
之类的替代setStackFrom(position)
的方法。 ScrollToPosition不起作用,如果我在itemCount仍为0时调用它,则它将被忽略。如果我在通知itemCount现在> 0时调用它,它将从0开始布局,并在跳转到目标位置后不久跳转。如果我使用DiffUtil.DiffResult.dispatchUpdatesTo(adapter)`,它将完全失败。 (从0开始显示,然后滚动到目标位置,然后再次回到位置0)
答案 0 :(得分:4)
我自己找到了解决方法:
我扩展了LayoutManager:
class MyLayoutManager extends LinearLayoutManager {
private int mPendingTargetPos = -1;
private int mPendingPosOffset = -1;
@Override
public void onLayoutChildren(Recycler recycler, State state) {
if (mPendingTargetPos != -1 && state.getItemCount() > 0) {
/*
Data is present now, we can set the real scroll position
*/
scrollToPositionWithOffset(mPendingTargetPos, mPendingPosOffset);
mPendingTargetPos = -1;
mPendingPosOffset = -1;
}
super.onLayoutChildren(recycler, state);
}
@Override
public void onRestoreInstanceState(Parcelable state) {
/*
May be needed depending on your implementation.
Ignore target start position if InstanceState is available (page existed before already, keep position that user scrolled to)
*/
mPendingTargetPos = -1;
mPendingPosOffset = -1;
super.onRestoreInstanceState(state);
}
/**
* Sets a start position that will be used <b>as soon as data is available</b>.
* May be used if your Adapter starts with itemCount=0 (async data loading) but you need to
* set the start position already at this time. As soon as itemCount > 0,
* it will set the scrollPosition, so that given itemPosition is visible.
* @param position
* @param offset
*/
public void setTargetStartPos(int position, int offset) {
mPendingTargetPos = position;
mPendingPosOffset = offset;
}
}
它可以存储目标位置。如果onLayoutChildren
被RecyclerView调用,它将检查itemCount是否已经大于0。如果为true,它将调用scrollToPositionWithOffset()
。
所以我可以立即告诉什么位置应该可见,但是在Adapter中的位置存在之前不会告诉LayoutManager。
答案 1 :(得分:0)
您可以尝试一下,它将滚动到您想要的位置:
rv.getLayoutManager().scrollToPosition(positionInTheAdapter).
答案 2 :(得分:0)
如果要滚动到特定位置并且该位置是适配器的位置,则可以使用StaggeredGridLayoutManager
scrollToPosition
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
staggeredGridLayoutManager.scrollToPosition(10);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
如果我理解这个问题,您想滚动到一个特定的位置,但是该位置是适配器的位置,而不是RecyclerView的项目位置。
您只能通过LayoutManager
实现这一目标。
rv.getLayoutManager().scrollToPosition(youPositionInTheAdapter);
答案 3 :(得分:0)
以上方法都不适合我。我使用ViewTreeObserver进行了以下操作,一旦添加了它的子级/更改了它的可见性,就会触发它。
recyclerView.apply {
adapter = ...
layoutManager = ...
val itemCount = adapter?.itemCount ?: 0
if(itemCount > 1) {
viewTreeObserver.addOnGlobalLayoutListener(object: ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewTreeObserver.removeOnGlobalLayoutListener(this)
(layoutManager as? LinearLayoutManager)?.scrollToPosition(#PositionToStartAt)
}
}
}
继续,并将#PositionToStartAt设置为特定值。您还可以确保在布置了特定数量的子级以确保正确设置后触发RecyclerView初始位置设置。
if(recyclerView.childCount() > #PositionCheck) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
(layoutManager as? LinearLayoutManager)?.scrollToPosition(#PositionToStartAt)
}
答案 4 :(得分:0)
如果您唯一的动机是从特定位置启动recyclerView而没有任何类似滚动的动画,我建议您使用 StaggeredGridLayoutManager
val staggeredGridLayoutManager = StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.HORIZONTAL)//or VERTICAL
staggeredGridLayoutManager.scrollToPosition(specificPosition)
recyclerView.apply{
layoutManager = staggeredGridLayoutManager
}
答案 5 :(得分:0)
对一个长期存在的问题的另一个贡献......
如前所述,layoutManager.scrollToPosition/WithOffset()
确实可以定位 RecyclerView,但时间安排可能很棘手。例如,对于可变长度的项目视图,RecyclerView 必须努力工作才能测量所有先前的项目视图。
下面的方法只是延迟告诉 RecyclerView 有关先前项目的信息,然后调用 notifyItemRangeInserted(0, offset)
。这使视图能够出现在正确的位置,在开始时没有可见的滚动,并准备好向后滚动。
private List<Bitmap> bitmaps = new ArrayList<>();
...
private class ViewAdapter extends RecyclerView.Adapter<ViewHolder> {
private volatile int offset;
private boolean offsetCancelled;
ViewAdapter(int initialOffset) {
this.offset = initialOffset;
this.offsetCancelled = initialOffset > 0;
}
@Override
public int getItemCount() {
return bitmaps.size() - offset;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ImageView imageView = new ImageView(MyActivity.this); // Or getContext() from a Fragment
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
imageView.setLayoutParams(lp);
return new ViewHolder(imageView);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.imageView.setImageBitmap(bitmaps.get(position + offset));
if (!offsetCancelled) {
offsetCancelled = true;
recyclerView.post(() -> {
int previousOffset = offset;
offset = 0;
notifyItemRangeInserted(0, previousOffset);
Log.i(TAG, "notifyItemRangeInserted(0, " + previousOffset + ")");
});
}
}
}
private class ViewHolder extends RecyclerView.ViewHolder {
private final ImageView imageView;
ViewHolder(@NonNull ImageView itemView) {
super(itemView);
this.imageView = itemView;
}
}
对于上下文,这是一个完整的示例,基于 ImageView 和 Bitmap。关键位是使用 getItemCount()
和 onBindViewHolder()
中的偏移量。