我有一个非常标准的 RecyclerView
和一个垂直的 LinearLayoutManager
。我一直在顶部插入新项目,我正在调用 notifyItemInserted(0)
。
我希望列表保持滚动到顶部;始终显示第 0 个位置。
从我的要求来看,LayoutManager
的行为会根据项目的数量而有所不同。
虽然所有项目都适合屏幕,但它的外观和行为符合我的预期:新项目总是出现在顶部并将所有内容移动在它下面。
但是,一旦没有。项目超出了 RecyclerView 的边界,新项目被添加到当前可见的项目之上,但可见项目仍在 View 中。用户必须滚动才能看到最新的项目。
这种行为对于许多应用程序来说是完全可以理解的并且很好,但对于“实时提要”来说却不是这样,在这种情况下,看到最新的内容比使用自动滚动“不分散”用户的注意力更重要。
我知道这个问题几乎与 Adding new item to the top of the RecyclerView 重复...但所有建议的答案都只是变通办法(无可否认,其中大多数都非常好)。
我正在寻找一种方法来真正改变这种行为。我希望 LayoutManager 行为完全相同,无论项目数量如何。我希望它始终移动所有项目(就像它对前几个添加项所做的那样),而不是在某个时候停止移动项目,并通过平滑滚动列表到顶部来补偿。
基本上,没有smoothScrollToPosition
,没有RecyclerView.SmoothScroller
。子类化 LinearLayoutManager
没问题。我已经在研究它的代码,但到目前为止没有任何运气,所以我决定问问是否有人已经处理过这个问题。感谢您的任何想法!
编辑:澄清为什么我拒绝回答链接问题:主要是我担心动画流畅度。
请注意第一个 GIF,其中 ItemAnimator
在添加新项目的同时移动其他项目,淡入和移动动画的持续时间相同。但是,当我通过平滑滚动“移动”项目时,我无法轻松控制滚动速度。即使使用默认的 ItemAnimator
持续时间,这看起来也不太好,但在我的特殊情况下,我什至需要减慢 ItemAnimator
的持续时间,这使情况变得更糟:
最佳答案
虽然我写了这个答案并且这是公认的解决方案,但我建议在尝试之前先看看其他后来的答案,看看它们是否适合您。
当一个项目被添加到 RecyclerView
的顶部并且该项目可以适合屏幕时,该项目将附加到 View 持有者并且 RecyclerView
会进行动画阶段向下移动项目以在顶部显示新项目。
如果不滚动就无法显示新项目,则不会创建 View 持有者,因此没有任何动画。发生这种情况时,将新项目放到屏幕上的唯一方法是滚动,这会导致创建 View 持有者,以便可以在屏幕上布置 View 。 (似乎确实存在部分显示 View 并创建 View 持有者的边缘情况,但我将忽略这个特定实例,因为它不相关。)
因此,问题是两个不同的 Action ,添加 View 的动画和添加 View 的滚动,必须让用户看起来相同。我们可以深入研究底层代码并弄清楚 View 持有者创建、动画计时等方面到底发生了什么。但是,即使我们可以复制这些操作,如果底层代码发生变化,它也可能会中断。这就是你所抵制的。
另一种方法是在 RecyclerView
的零位置添加标题。当显示此标题并将新项目添加到位置 1 时,您将始终看到动画。如果您不想要标题,可以将其设为零高度,它不会显示。以下视频展示了这种技术:
这是演示代码。它只是在项目的位置 0 处添加一个虚拟条目。如果您不喜欢虚拟条目,还有其他方法可以解决这个问题。您可以搜索将 header 添加到 RecyclerView
的方法。
(如果您确实使用滚动条,它会出现异常,您可能从演示中可以看出。要 100% 解决此问题,您将不得不接管大量滚动条高度和位置计算。自定义LinearLayoutManager
的 computeVerticalScrollOffset()
负责在适当的时候将滚动条放在顶部。(代码是在拍摄视频后引入的。)然而,滚动条在向下滚动时跳转。更好的放置计算会解决这个问题。有关在不同高度项目的上下文中滚动条的更多信息,请参阅 this Stack Overflow question。)
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>
关于android - 在零位置插入 RecyclerView 项目 - 始终保持滚动到顶部,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50293660/