oli*_*i.G 13 android android-layout android-recyclerview
我有一个非常标准RecyclerView的垂直LinearLayoutManager.我一直在顶部插入新物品,我正在打电话notifyItemInserted(0).
我希望列表保持滚动到顶部 ; 始终显示第0个位置.
从我的要求来看,LayoutManager根据项目数量的不同表现.
虽然所有项目都适合屏幕,但它的外观和行为与我预期的一样:新项目始终显示在顶部,并将所有项目都移到它下面.
但是,一旦没有.项目超出RecyclerView的范围,新项目将添加到当前可见项目之上,但可见项目仍保留在视图中.用户必须滚动才能看到最新的项目.
对于许多应用程序来说,这种行为是完全可以理解的,但不适用于"实时馈送",其中看到最近的事情比使用自动滚动"不分散"用户更重要.
我知道这个问题几乎与在RecyclerView的顶部添加新项目相同 ......但是所有建议的答案都只是解决方法(大多数都非常好,不可否认).
我正在寻找一种方法来实际改变这种行为.我希望LayoutManager的行为完全相同,无论项目数量多少.我希望它总是移动所有项目(就像它为前几个添加项目所做的那样),而不是停止在某些时候移动项目,并通过平滑滚动列表到顶部进行补偿.
基本上,不smoothScrollToPosition,不RecyclerView.SmoothScroller.子类化LinearLayoutManager很好.我已经挖掘了它的代码,但到目前为止没有任何运气,所以我决定询问以防有人已经处理过这个问题.谢谢你的任何想法!
编辑:澄清为什么我从相关问题中驳回答案:主要是我关注动画的平滑性.
请注意,在第一个GIF中,ItemAnimator在添加新项目的同时移动其他项目,淡入和移动动画都具有相同的持续时间.但是当我通过平滑滚动"移动"项目时,我无法轻易控制滚动的速度.即使使用默认ItemAnimator持续时间,这看起来也不太好,但在我的特定情况下,我甚至需要减慢ItemAnimator持续时间,这会使情况变得更糟:
Che*_*amp 11
将某项添加到的顶部RecyclerView并且该项可以放到屏幕上时,该项将附加到视图支架并RecyclerView经历动画阶段以向下移动项以在顶部显示新项。
如果无法滚动显示新项目,则不会创建视图持有者,因此没有要进行动画处理的内容。发生这种情况时,将新项目显示在屏幕上的唯一方法是滚动浏览,这将导致创建视图持有人,以便可以将视图放置在屏幕上。(在某些情况下,似乎确实会出现部分显示视图并创建视图持有人的情况,但是由于它与外观无关,因此我将忽略此特定实例。)
因此,问题在于必须做出两个不同的动作,即添加视图的动画和滚动视图的滚动,以使用户看起来相同。我们可以深入研究底层代码,并准确地了解在创建视图持有者,动画定时等方面发生了什么。但是,即使我们可以重复这些操作,但是如果底层代码发生更改,它也可能会中断。这就是您所抵抗的。
另一种方法是在的位置零处添加标头RecyclerView。显示此标题并将新项目添加到位置1时,您将始终看到动画。如果您不希望标题,可以将其设置为零高度,并且不会显示。以下视频显示了此技术:
这是演示的代码。它只是在项目的位置0添加一个虚拟条目。如果您不喜欢虚拟条目,则可以使用其他方法来实现。您可以搜索将标头添加到的方法RecyclerView。
(如果你使用一个滚动条,它会表现不好,你也许可以从演示出来。为了解决这个问题100%,你将不得不采取了很多的滚动条的高度和位置计算,自定义computeVerticalScrollOffset()的LinearLayoutManager需要摆放的护理滚动条会在向下滚动时跳动。更好的布局计算会解决此问题。有关此滚动条的更多信息,请参见此堆栈溢出问题。不同高度的物品的上下文。)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
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>
Run Code Online (Sandbox Code Playgroud)
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>
Run Code Online (Sandbox Code Playgroud)
我还显示项目的实时提要,当添加新项目时,它位于第一个项目之前。但是在回收站视图中,我需要向上滚动才能看到新项目。
要解决该问题,请添加到 Adapter aRecyclerView.AdapterDataObserver()并覆盖onItemRangeInserted(). 添加新数据时,检查数据是否位于位置 0,并且回收器视图位于顶部(如果您在列表中滚动,则不想自动滚动到第一个位置)。
例子:
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);
}
}
});
Run Code Online (Sandbox Code Playgroud)
这个解决方案对我来说很好用,有不同类型的适配器,比如ListAdapter和PagedListAdapter。我首先想使用已接受解决方案的类似实现,在其中添加一个愚蠢的第一项,但这PagedListAdapter是不可能的,因为列表是不可变的。