简体   繁体   English

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

[英]Inserting RecyclerView items at zero position - always stay scrolled to top

I have a pretty standard RecyclerView with a vertical LinearLayoutManager .我有一个非常标准的RecyclerView和一个垂直的LinearLayoutManager I keep inserting new items at the top and I'm calling notifyItemInserted(0) .我一直在顶部插入新项目,我正在调用notifyItemInserted(0)

I want the list to stay scrolled to the top ;我希望列表保持滚动到顶部 to always display the 0th position.始终显示第 0 个位置。

From my requirement's point of view, the LayoutManager behaves differently based on the number of items.从我的需求的角度来看, LayoutManager行为会根据项目的数量而有所不同。

While all items fit on the screen, it looks and behaves as I expect: The new item always appears on top and shifts everything below it.虽然所有项目都适合屏幕,但它的外观和行为都符合我的预期:新项目总是出现在顶部并移动它下面的所有内容。

少数项目的行为:添加将其他项目向下移动,第一个(最新)项目始终可见

However, as soon as the no.然而,一旦没有。 of items exceeds the RecyclerView's bounds, new items are added above the currently visible one, but the visible items stay in view .的项目超出了 RecyclerView 的边界,新项目添加到当前可见项目之上,但可见项目保持在视图中 The user has to scroll to see the newest item.用户必须滚动才能看到最新的项目。

许多项目的行为:新项目添加到顶部边界上方,用户必须滚动才能显示它们

This behavior is totally understandable and fine for many applications, but not for a "live feed", where seeing the most recent thing is more important than "not distracting" the user with auto-scrolls.对于许多应用程序来说,这种行为是完全可以理解的并且很好,但对于“实时提要”则不然,在这种情况下,查看最新内容比“不分散”自动滚动用户的注意力更重要。


I know this question is almost a duplicate of Adding new item to the top of the RecyclerView ... but all of the proposed answers are mere workarounds (most of them quite good, admittedly).我知道这个问题几乎是将新项目添加到 RecyclerView 顶部的重复......但所有建议的答案都只是解决方法(不可否认,其中大多数都很好)。

I'm looking for a way to actually change this behavior .我正在寻找一种方法来真正改变这种行为 I want the LayoutManager to act exactly the same, no matter the number of items .我希望 LayoutManager 的行为完全相同,无论项目数量如何 I want it to always shift all of the items (just like it does for the first few additions), not to stop shifting items at some point, and compensate by smooth-scrolling the list to the top.我希望它始终移动所有项目(就像它对前几个添加所做的那样),而不是在某个时候停止移动项目,并通过将列表平滑滚动到顶部来进行补偿。

Basically, no smoothScrollToPosition , no RecyclerView.SmoothScroller .基本上,没有smoothScrollToPosition ,没有RecyclerView.SmoothScroller Subclassing LinearLayoutManager is fine.继承LinearLayoutManager很好。 I'm already digging through its code, but without any luck so far, so I decided to ask in case someone already dealt with this.我已经在挖掘它的代码,但到目前为止没有任何运气,所以我决定问问是否有人已经处理过这个问题。 Thanks for any ideas!感谢您的任何想法!


EDIT: To clarify why I'm dismissing answers from the linked question: Mostly I'm concerned about animation smoothness.编辑:澄清为什么我不理会链接问题的答案:主要是我担心动画平滑度。

Notice in the first GIF where ItemAnimator is moving other items while adding the new one, both fade-in and move animations have the same duration.请注意,在第一个 GIF 中, ItemAnimator在添加新项目的同时移动其他项目,淡入和移动动画具有相同的持续时间。 But when I'm "moving" the items by smooth scrolling, I cannot easily control the speed of the scroll .但是当我通过平滑滚动“移动”项目时,我无法轻松控制滚动速度 Even with default ItemAnimator durations, this doesn't look as good, but in my particular case, I even needed to slow down the ItemAnimator durations, which makes it even worse:即使使用默认的ItemAnimator持续时间,这看起来也不那么好,但在我的特殊情况下,我什至需要减慢ItemAnimator持续时间,这使得情况更糟:

插入“固定”,平滑滚动 + ItemAnimator 持续时间增加

Although I wrote this answer and this is the accepted solution, I suggest a look at the other later answers to see if they work for you before attempting this.尽管我写了这个答案并且这是公认的解决方案,但我建议在尝试此之前查看其他稍后的答案,看看它们是否适合您。


When an item is added to the top of the RecyclerView and the item can fit onto the screen, the item is attached to a view holder and RecyclerView undergoes an animation phase to move items down to display the new item at the top.当一个项目被添加到RecyclerView的顶部并且该项目可以适应屏幕时,该项目被附加到一个视图持有者并且RecyclerView经历一个动画阶段来向下移动项目以在顶部显示新项目。

If the new item cannot be displayed without scrolling, a view holder is not created so there is nothing to animate.如果不滚动就无法显示新项目,则不会创建视图持有者,因此没有任何动画。 The only way to get the new item onto the screen when this happens is to scroll which causes the view holder to be created so the view can be laid out on the screen.发生这种情况时将新项目放到屏幕上的唯一方法是滚动,这会导致创建视图持有者,以便可以在屏幕上布置视图。 (There does seem to be an edge case where the view is partially displayed and a view holder is created, but I will ignore this particular instance since it is not germane.) (似乎确实存在局部显示视图并创建视图持有者的边缘情况,但我将忽略此特定实例,因为它不是密切相关的。)

So, the issue is that two different actions, animation of an added view and scrolling of an added view, must be made to look the same to the user.因此,问题是两个不同的动作,添加视图的动画和添加视图的滚动,必须让用户看起来相同。 We could dive into the underlying code and figure out exactly what is going on in terms of view holder creation, animation timing, etc. But, even if we can duplicate the actions, it can break if the underlying code changes.我们可以深入研究底层代码,并确切地弄清楚在视图持有者创建、动画计时等方面发生了什么。但是,即使我们可以复制这些动作,如果底层代码发生变化,它也可能会中断。 This is what you are resisting.这就是你在抗拒的。

An alternative is to add a header at position zero of the RecyclerView .另一种方法是在RecyclerView零位置添加标题。 You will always see the animation when this header is displayed and new items are added to position 1. If you don't want a header, you can make it zero height and it will not display.当显示此标题并将新项目添加到位置 1 时,您将始终看到动画。如果您不想要标题,您可以将其设置为零高度,它不会显示。 The following video shows this technique:以下视频展示了这种技术:

[视频]

This is the code for the demo.这是演示的代码。 It simply adds a dummy entry at position 0 of the items.它只是在项目的位置 0 添加一个虚拟条目。 If a dummy entry is not to your liking, there are other ways to approach this.如果您不喜欢虚拟条目,还有其他方法可以解决此问题。 You can search for ways to add headers to RecyclerView .您可以搜索将标题添加到RecyclerView

(If you do use a scrollbar, it will misbehave as you can probably tell from the demo. To fix this 100%, you will have to take over a lot of the scrollbar height and placement computation. The custom computeVerticalScrollOffset() for the LinearLayoutManager takes care of placing the scrollbar at the top when appropriate. (Code was introduced after video taken.) The scrollbar, however, jumps when scrolling down. A better placement computation would take care of this problem. See this Stack Overflow question for more information on scrollbars in the context of varying height items.) (如果你确实使用了滚动条,它会像你从演示中看到的那样行为不端。要修复这个 100%,你将不得不接管大量的滚动条高度和位置计算。用于LinearLayoutManager的自定义computeVerticalScrollOffset()负责在适当的时候将滚动条放置在顶部。(在拍摄视频后引入了代码。)然而,滚动条在向下滚动时会跳转。更好的放置计算可以解决这个问题。有关更多信息,请参阅此堆栈溢出问题在不同高度项目的上下文中的滚动条上。)

MainActivity.java主活动.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活动_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 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>

I'm also displaying a live feed of items, and when a new item is added, it's before the first item.我还显示项目的实时提要,当添加新项目时,它位于第一个项目之前。 But in the recycler view, I need to scroll up to see the new item.但是在回收站视图中,我需要向上滚动才能看到新项目。

To solve the problem, add to the Adapter a RecyclerView.AdapterDataObserver() and override the onItemRangeInserted() .要解决此问题,请向适配器添加RecyclerView.AdapterDataObserver()并覆盖onItemRangeInserted() When new data is added, check if the data is on at position 0, and the recycler view was on top (You don't want to autoscroll to first position if you were scrolling in the list).添加新数据时,检查数据是否位于位置 0,并且回收器视图位于顶部(如果您在列表中滚动,则不想自动滚动到第一个位置)。

Exemple :例子:

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);
        }
    }
});

This solution worked fine for me, with different type of Adapter, like ListAdapter and PagedListAdapter .这个解决方案对我来说很好用,有不同类型的适配器,比如ListAdapterPagedListAdapter I firstly wanted to use a similar implementation of the accepted solution , where you add a dumb first item, but in PagedListAdapter it's impossible as list are immutable.我首先想使用已接受解决方案的类似实现,其中添加一个愚蠢的第一项,但在PagedListAdapter这是不可能的,因为列表是不可变的。

This worked for me:这对我有用:

val atTop = !recycler.canScrollVertically(-1)

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

if (atTop) {
    recycler.scrollToPosition(0)
}

The only solution that worked for me was to reverse the recycler's layout by calling setReverseLayout() and setStackFromEnd() on its LinearLayoutManager .唯一对我setReverseLayout()解决方案是通过在LinearLayoutManager上调用setReverseLayout()setStackFromEnd()来反转回收器的布局。

This might sound stupid, but the way RecyclerView handles adding items at the end of the list is what you need at the top.这听起来可能很愚蠢,但RecyclerView处理在列表末尾添加项目的方式正是您在顶部需要的。 The only downsize of this is that you'd have to reverse your list and start adding items to the end instead.唯一的缩小是您必须反转您的列表并开始将项目添加到末尾。

Use adapter.notifyDataSetChanged() instead of adater.notifyItemInserted(0) .使用adapter.notifyDataSetChanged()而不是adater.notifyItemInserted(0) This will scroll recylerView to zero position if current scroll position is one( old zero ).如果当前滚动位置为 1( old zero ),这会将recylerView滚动到零位置。

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

相关问题 如何检测 RecyclerView 何时滚动到最顶部位置 - How to detect when RecyclerView is scrolled to most top position 滚动RecyclerView滚动到始终位于顶部 - Scroll RecyclerView Scroll to position always on top recyclerview 滚动并留在旧 position 不要 go 顶部 - recyclerview scroll and stay in old position dont go top 滚动时 Recyclerview 更改项目 - Recyclerview changing items when is scrolled RecyclerView - 在 notifyItemInserted(0) 后保持在 0 位置 - RecyclerView - Stay on 0 position after notifiyItemInserted(0) 添加项目时,如何防止recyclerview自动滚动到最后一个项目的位置? - How to prevent a recyclerview from getting automatically scrolled to the last item's position when items are added to it? 滚动顶部时,Android RecyclerView StaggeredGrid项目会改变位置 - Android RecyclerView StaggeredGrid items change position when scrolling top 当recyclerview滚动/返回顶部时展开appbarlayout - Expand appbarlayout when recyclerview is scrolled/fling to top 向上滚动时,RecyclerView跳到顶部 - RecyclerView jump to top when scrolled up 检查RecyclerView是否在顶部滚动(findFirstCompletelyVisibleItemPosition将不起作用) - Check if RecyclerView is scrolled on top (findFirstCompletelyVisibleItemPosition will not work)
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM