当前位置: 首页 > 知识库问答 >
问题:

在零位置插入RecyclerView项-始终保持滚动到顶部

薛望
2023-03-14

我有一个非常标准的RecyclizerView,带有垂直的LinearLayoutManager。我一直在顶部插入新项目,并调用 notifyItemInserted(0)。<

我希望列表保持滚动到顶部;总是显示第0个位置。

从我的需求的角度来看,< code>LayoutManager根据项目的数量表现不同。

虽然所有项目都适合屏幕,但它的外观和行为与我预期的一样:新项目总是出现在顶部,并移动其下的所有项目。

但是,一旦项目数超过RecyclerView的界限,新项目就会添加到当前可见项目的上方,但可见项目仍会保留在视图中。用户必须滚动才能看到最新的项目。

这种行为是完全可以理解的,对于许多应用程序来说也很好,但对于“实时提要”来说就不是了,在“实时提要”,看到最新的东西比用自动滚动“不分散”用户注意力更重要。

我知道这个问题几乎是在RecyclerView的顶部添加新项目的重复...但是所有提出的答案都只是权宜之计(无可否认,其中大部分都相当不错)。

我正在寻找一种方法来真正改变这种行为。我希望LayoutManager的行为完全相同,无论项目数量如何。我希望它总是移动所有项目(就像它对前几个添加所做的那样),而不是在某个时候停止移动项目,并通过平滑地将列表滚动到顶部来进行补偿。

基本上,没有< code > smoothScrollToPosition ,没有< code > RecyclerView。SmoothScroller。子类化< code > linearrayoutmanager 就可以了。我已经在挖掘它的代码,但是到目前为止没有任何运气,所以我决定问一下,以防有人已经处理了这个问题。感谢任何想法!

编辑:为了澄清为什么我拒绝回答相关问题:我主要关心动画的平滑度。

请注意,在第一个GIF中,ItemAnimator在添加新项目的同时移动其他项目,淡入和移动动画的持续时间相同。但是当我通过平滑滚动“移动”项目时,我无法轻松控制滚动的速度。即使使用默认的ItemAnimator持续时间,这看起来也不太好,但在我的特定情况下,我甚至需要减慢ItemAnimator的持续时间,这使情况变得更糟:

共有3个答案

商松
2023-03-14

这对我有用:

val atTop = !recycler.canScrollVertically(-1)

adapter.addToFront(item)
adapter.notifyItemInserted(0)

if (atTop) {
    recycler.scrollToPosition(0)
}
袁宜民
2023-03-14

我还显示了一个项目的实时提要,当添加一个新项目时,它位于第一个项目之前。但在回收器视图中,我需要向上滚动以查看新项目。

要解决这个问题,请向适配器添加一个< code > RecyclerView。AdapterDataObserver()并覆盖< code > onItemRangeInserted()。添加新数据时,检查数据是否在位置0,回收器视图是否在顶部(如果在列表中滚动,您不希望自动滚动到第一个位置)。

例如:

adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {
        super.onItemRangeInserted(positionStart, itemCount);
        if (positionStart == 0 && positionStart == layoutManager.findFirstCompletelyVisibleItemPosition()) {
            layoutManager.scrollToPosition(0);
        }
    }
});

这个解决方案对我来说很好,使用不同类型的适配器,如ListAdapterPagedListAdapter。我首先想使用已接受解决方案的类似实现,在其中添加一个愚蠢的第一项,但在PagedListAdapter中,这是不可能的,因为列表是不可变的。

潘涵煦
2023-03-14

虽然我写了这个答案,而且这是公认的解决方案,但我建议在尝试之前先看看其他的答案,看看它们是否对你有用。

当一个项目被添加到<code>RecyclerView

如果新项目在不滚动的情况下无法显示,则不会创建视图持有者,因此没有任何动画。当这种情况发生时,将新项目放到屏幕上的唯一方法是滚动,这会导致创建视图持有者,以便视图可以在屏幕上布局。(似乎确实存在视图部分显示并创建视图持有者的边缘情况,但我将忽略这个特定实例,因为它与此无关。)

因此,问题是两个不同的动作,添加视图的动画和添加视图的滚动,必须让用户看起来相同。我们可以深入到底层代码中,并精确地计算出视图控件创建、动画计时等方面发生了什么。但是,即使我们可以复制这些动作,如果底层代码发生变化,它也可能会中断。这就是你所抗拒的。

另一种方法是在< code > recycle view 的零位置添加一个标头。当显示此标题并将新项目添加到位置1时,您将始终看到动画。如果你不想要一个标题,你可以把它设置为零高度,这样它就不会显示了。以下视频展示了这种技术:

这是演示的代码。它只是在项目的位置0添加了一个虚拟条目。如果你不喜欢虚拟条目,还有其他方法。您可以搜索将标题添加到< code > recycle view 的方法。

(如果您确实使用了滚动条,它将出现错误,正如您可能从演示中看到的。要100%修复此问题,您必须接管大量的滚动条高度和位置计算。<code>LinearLayoutManager</code>的自定义<code>computeVerticalScrollOffset()

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private TheAdapter mAdapter;
    private final ArrayList<String> mItems = new ArrayList<>();
    private int mItemCount = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    LinearLayoutManager layoutManager =
        new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) {
            @Override
            public int computeVerticalScrollOffset(RecyclerView.State state) {
                if (findFirstCompletelyVisibleItemPosition() == 0) {
                    // Force scrollbar to top of range. When scrolling down, the scrollbar
                    // will jump since RecyclerView seems to assume the same height for
                    // all items.
                    return 0;
                } else {
                    return super.computeVerticalScrollOffset(state);
                }
            }
        };
        recyclerView.setLayoutManager(layoutManager);

        for (mItemCount = 0; mItemCount < 6; mItemCount++) {
            mItems.add(0, "Item # " + mItemCount);
        }

        // Create a dummy entry that is just a placeholder.
        mItems.add(0, "Dummy item that won't display");
        mAdapter = new TheAdapter(mItems);
        recyclerView.setAdapter(mAdapter);
    }

    @Override
    public void onClick(View view) {
        // Always at to position #1 to let animation occur.
        mItems.add(1, "Item # " + mItemCount++);
        mAdapter.notifyItemInserted(1);
    }
}

TheAdapter.java

class TheAdapter extends RecyclerView.Adapter<TheAdapter.ItemHolder> {
    private ArrayList<String> mData;

    public TheAdapter(ArrayList<String> data) {
        mData = data;
    }

    @Override
    public ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view;

        if (viewType == 0) {
            // Create a zero-height view that will sit at the top of the RecyclerView to force
            // animations when items are added below it.
            view = new Space(parent.getContext());
            view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0));
        } else {
            view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.list_item, parent, false);
        }
        return new ItemHolder(view);
    }

    @Override
    public void onBindViewHolder(final ItemHolder holder, int position) {
        if (position == 0) {
            return;
        }
        holder.mTextView.setText(mData.get(position));
    }

    @Override
    public int getItemViewType(int position) {
        return (position == 0) ? 0 : 1;
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }

    public static class ItemHolder extends RecyclerView.ViewHolder {
        private TextView mTextView;

        public ItemHolder(View itemView) {
            super(itemView);
            mTextView = (TextView) itemView.findViewById(R.id.textView);
        }
    }
}

activity_main.xml

<android.support.constraint.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:scrollbars="vertical"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:text="Button"
        android:onClick="onClick"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>

list_item.xml

<LinearLayout 
    android:id="@+id/list_item"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    android:orientation="horizontal">

    <View
        android:id="@+id/box"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginStart="16dp"
        android:background="@android:color/holo_green_light"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@id/box"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="TextView" />

</LinearLayout>
 类似资料:
  • 问题内容: 我的问题是当我在回收视图中滚动数据时,所以加载了新数据,但问题是当新数据加载时,回收视图从第一个产品开始, 我需要保持旧的位置,只滚动新数据,而不滚动所有旧数据 目前,我正在添加文件adeptoer文件和主文件的完整代码, 问题答案: 试试这个:

  • 问题内容: 我如何避免滚动页面的其余部分?我考虑过使用和,只是想知道是否有更简单,更用户友好的方法,这样做的最佳实践是什么? 问题答案: 使用的包含您的头,喜欢的东西 在此示例中,当开始时位于下方100px处,但随着用户滚动,它保持原位。当然,不用说,您需要确保有背景,以便当两个s重叠时,其内容实际上是可见的。

  • 问题内容: 这是我的onCreate,它处理适配器,LinearLayoutManager等。我尝试了每个选项以尝试将其滚动到顶部,但不能。我什至尝试创建自己的自定义LinearLayoutManager。 问题答案: 您尝试过还是? RecyclerView不会在其中实现任何滚动逻辑,而是更加具体,它会根据实现的布局转到特定的索引,在您的情况下是线性的。

  • 如何刷新显示在中的数据(在其适配器上调用),并确保滚动位置被重置到它原来的位置? 如果是良好的ol'则只需检索,检查其,然后使用相同的精确数据调用。 在的情况下,这似乎是不可能的。 我想我应该使用它的,它确实提供了,但是检索位置和偏移量的正确方法是什么? 和? 还是有更优雅的方法来完成这项工作?

  • 问题内容: 我正在MVC中从事一个项目,并且对此有所了解。有一些成长的烦恼,但一旦您找出来,那还不错。在WebForms世界中,真正简单的一件事是保持页面上的滚动位置。您所要做的只是将MaintenanceScrollPositionOnPostback属性设置为true。但是,在MVC中,我不使用回发,因此这对我不起作用。处理此问题的标准方法是什么? 编辑: Ajax是可以接受的,但我也想知道如

  • 我有一个和一个,它由一个带有不同高度项的适配器支持。有没有办法告诉设置滚动位置,使项目X(或多或少)准确地出现在屏幕底部?