在零位置插入RecyclerView项目-始终保持滚动到顶部 [英] Inserting RecyclerView items at zero position - always stay scrolled to top
问题描述
我有一个标准的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屋!