在零位置插入RecyclerView项目-始终保持滚动到顶部 [英] Inserting RecyclerView items at zero position - always stay scrolled to top

查看:72
本文介绍了在零位置插入RecyclerView项目-始终保持滚动到顶部的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个标准的RecyclerView和一个垂直的LinearLayoutManager.我一直在顶部插入新项目,然后打电话给notifyItemInserted(0).

I have a pretty standard RecyclerView with a vertical LinearLayoutManager. I keep inserting new items at the top and I'm calling notifyItemInserted(0).

我希望列表一直滚动到顶部;始终显示第0个位置.

I want the list to stay scrolled to the top; to always display the 0th position.

从我的需求的角度来看,LayoutManager的行为根据项目的数量而有所不同.

From my requirement's point of view, the LayoutManager behaves differently based on the number of items.

虽然所有项目都适合屏幕,但其外观和行为符合我的预期:新项目始终显示在顶部,并将所有内容移到其下方.

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.

但是,一旦没有.项超出了RecyclerView的范围,新项已添加到当前可见项的上方,但可见项仍在视图中.用户必须滚动才能看到最新的项目.

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. 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.

我知道这个问题几乎与>添加新问题到RecyclerView顶部的项目 ...,但所有建议的答案都只是解决方法(坦率地说,大多数方法都很好).

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).

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

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. 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.

基本上,没有smoothScrollToPosition,没有RecyclerView.SmoothScroller.子类化LinearLayoutManager很好.我已经在研究其代码,但是到目前为止还没有任何运气,所以我决定询问是否有人已经对此进行了处理.感谢您的任何想法!

Basically, no smoothScrollToPosition, no RecyclerView.SmoothScroller. Subclassing LinearLayoutManager is fine. 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!

编辑:为了阐明为什么我不考虑链接问题的答案:大多数情况下,我担心动画的平滑度.

To clarify why I'm dismissing answers from the linked question: Mostly I'm concerned about animation smoothness.

在第一个GIF中注意,其中ItemAnimator在添加新项目时正在移动其他项目,淡入和移动动画的持续时间相同.但是,当我通过平滑滚动移动"项目时,我无法轻松控制滚动速度.即使使用默认的ItemAnimator持续时间,效果也不佳,但是在我的特殊情况下,我什至需要放慢ItemAnimator持续时间,这更加糟糕:

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. 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:

推荐答案

将某项添加到RecyclerView的顶部并且该项可以放在屏幕上时,该项将附加到视图支架和进入动画阶段,将项目下移以在顶部显示新项目.

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.

如果新项目无法滚动显示,则不会创建视图持有者,因此无动画内容.发生这种情况时,将新项目显示在屏幕上的唯一方法是滚动浏览,这将导致创建视图持有人,以便可以将视图放置在屏幕上. (在某些情况下,似乎确实会部分显示视图并创建了视图持有人,但由于它与外观无关,因此我将忽略此特定实例.)

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.

另一种方法是在RecyclerView的零位置添加标头.显示此标题并将新项目添加到位置1时,您将始终看到动画.如果您不希望标题,可以将其设置为零高度,并且不会显示.以下视频展示了此技术:

An alternative is to add a header at position zero of the 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. The following video shows this technique:

这是演示的代码.它只是在项目的位置0添加一个虚拟条目.如果您不喜欢虚拟条目,则可以使用其他方法来实现.您可以搜索将标头添加到RecyclerView的方法.

This is the code for the demo. It simply adds a dummy entry at position 0 of the items. 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.

(如果您确实使用了滚动条,则可能会从演示中看出来,它的行为不正确.要修复此100%的问题,您将不得不承担很多滚动条的高度和位置计算.自定义computeVerticalScrollOffset()computeVerticalScrollOffset()会在适当时将滚动条放在顶部(录制视频后会引入代码.)但是,滚动条在向下滚动时会跳转,更好的布局计算可以解决此问题.请参阅此堆栈溢出问题,以获取有关以下内容的滚动条的更多信息:高度不同的项目.)

(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.)

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>

这篇关于在零位置插入RecyclerView项目-始终保持滚动到顶部的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆