虽然我写了这个答案并且它是被接受的解决方案,但在尝试此方法之前,我建议查看其他后来的答案,看看它们是否适用于您。
当一个项目被添加到RecyclerView
的顶部并且该项目可以适合屏幕时,该项目会被附加到一个视图持有者上,并且RecyclerView
会经历动画阶段以将项目向下移动以显示新项目在顶部。
如果新项目不能在不滚动的情况下显示,则不会创建视图持有者,因此没有任何内容可供动画。当发生这种情况时,将新项目放在屏幕上的唯一方法是滚动,这会导致创建视图持有者,以便可以在屏幕上布置视图。(似乎存在一种边缘情况,其中视图部分显示并创建视图持有者,但我将忽略此特定实例,因为它与本文无关。)
因此,问题在于必须使两个不同的操作,即添加视图的动画和滚动添加视图,对用户看起来相同。我们可以深入研究底层代码,并找出在视图持有者创建、动画定时等方面发生的确切情况。但是,即使我们可以复制这些操作,如果底层代码发生更改,它也可能会出现问题。这就是您所抵制的。
另一种选择是在RecyclerView
的位置0处添加一个标题。当此标题显示并且新项目被添加到位置1时,您将始终看到动画效果。如果您不想要标题,您可以将其高度设置为零,它将不会显示。以下视频展示了这种技术:
![[video]](https://istack.dev59.com/1o6uO.gif)
这是演示的代码。它只是在项目的位置0添加了一个虚拟条目。如果虚拟条目不符合您的喜好,还有其他方法可以解决此问题。您可以搜索如何向RecyclerView
添加标题。
(如果您使用滚动条,它会表现不良,从演示中您可能已经注意到了。要完全解决此问题,您将不得不接管大量滚动条高度和放置计算。自定义的computeVerticalScrollOffset()
用于LinearLayoutManager
在适当时将滚动条放置在顶部。 (代码是在录制视频之后引入的。)然而,滚动向下时滚动条会跳动。更好的放置计算将解决此问题。有关不同高度项目上下文中滚动条的更多信息,请参见此Stack Overflow问题。)
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) {
return 0;
} else {
return super.computeVerticalScrollOffset(state);
}
}
};
recyclerView.setLayoutManager(layoutManager);
for (mItemCount = 0; mItemCount < 6; mItemCount++) {
mItems.add(0, "Item # " + mItemCount);
}
mItems.add(0, "Dummy item that won't display");
mAdapter = new TheAdapter(mItems);
recyclerView.setAdapter(mAdapter);
}
@Override
public void onClick(View view) {
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) {
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>