繁体   English   中英

如何告诉 RecyclerView 从特定项目位置开始

[英]How to tell RecyclerView to start at specific item position

我希望我的带有 LinearLayoutManager 的 RecyclerView 在适配器更新后显示特定项目的滚动位置。 (不是第一个/最后一个位置)表示第一个(重新)布局,这个给定的位置应该在可见区域。 它不应该以位置 0 在顶部进行布局,然后滚动到目标位置。

我的适配器从 itemCount=0 开始,在线程中加载它的数据并稍后通知它的实际计数。 但是开始位置必须已经设置,而计数仍然为0!

截至目前,我使用了某种包含scrollToPositionpost Runnable ,但这有副作用(从第一个位置开始并立即跳转到目标位置(0 -> 目标),并且似乎不适用于 DiffUtil(0 -> 目标 -> 0 ))

编辑:清除:我需要替代layoutManager.setStackFromEnd(true); ,类似于setStackFrom(position) ScrollToPosition 不起作用,如果我在 itemCount 仍为 0 时调用它,它会被忽略。 如果我在通知 itemCount 现在 >0 时调用它,它将从 0 开始布局,然后短跳到目标位置。 如果我使用 DiffUtil.DiffResult.dispatchUpdatesTo(adapter)`,它会完全失败。 (从 0 开始显示,然后滚动到目标位置,然后再次回到位置 0)

我自己找到了一个解决方案:

我扩展了 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。如果为真,它会调用scrollToPositionWithOffset()

所以我可以立即知道哪个位置应该是可见的,但是在 Adapter 中存在位置之前不会告诉 LayoutManager。

你可以试试这个,它会滚动到你想要的位置:

rv.getLayoutManager().scrollToPosition(positionInTheAdapter).

如果要滚动到特定位置并且该位置是适配器的位置,则可以使用StaggeredGridLayoutManager scrollToPosition

StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
       staggeredGridLayoutManager.scrollToPosition(10);
       recyclerView.setLayoutManager(staggeredGridLayoutManager);

如果我理解这个问题,您想滚动到特定位置,但该位置是适配器的位置,而不是 RecyclerView 的项目位置。

您只能通过LayoutManager实现这一点。

rv.getLayoutManager().scrollToPosition(youPositionInTheAdapter);

以上方法都不适合我。 我使用 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)
}

如果您的唯一动机是从特定位置启动 recyclerView 而没有任何类似滚动的动画,我建议使用StaggeredGridLayoutManager

val staggeredGridLayoutManager = StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.HORIZONTAL)//or VERTICAL
            staggeredGridLayoutManager.scrollToPosition(specificPosition)

recyclerView.apply{
    layoutManager = staggeredGridLayoutManager
}

对一个长期存在的问题的另一个贡献......

如前所述, 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()使用偏移量。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM