ItemDecoration重写getItemOffsets()和动画

15

我正在使用GridLayoutManager覆盖getItemOffsets()方法,为我的RecyclerView应用相等的间距(请参见下面的代码)。

然而,当适配器中的一个对象被移除时,删除动画会在没有偏移量的情况下调用。因此,动画开始的位置与要删除的对象不同。

我尝试通过getSpanIndex(position)获取位置,但是当getItemOffsets()被调用时,由于该对象已从适配器中移除,因此位置(parent.getChildAdapterPosition(view))返回NO_POSITION

在我的情况下,有没有办法获得偏移量?

@Override
public void getItemOffsets(Rect outRect, View view, 
                     RecyclerView parent, RecyclerView.State state) {

    GridLayoutManager mgr = parent.getLayoutManager();
    int position = parent.getChildAdapterPosition(view);

    if (position == RecyclerView.NO_POSITION) {
       // here I need to access the position of the current element
       // and call outRect.set(left, top , right, bottom);
       // which is not possible because it is no longer in the adapter
        return;
    } 

    int spanCount = mgr.getSpanCount();
    int spanSize = mgr.getSpanSizeLookup().getSpanSize(position);
    int spanIndex = mgr.getSpanSizeLookup().getSpanIndex(position, spanCount);

    if (spanIndex == spanCount-1) {
        // last element
        left = space / 2;
        right = space;
    } else if (spanIndex == 0) {
        // first element
        left = space;
        right = space / 2;
    } else {
        // middle element
        left = space / 2;
        right = space / 2;
    }
    outRect.set(left, top, right, bottom);
}

请问您在移除一个项目时能否澄清一下问题?如果您移除一个元素,接下来的元素会动画填充空位,那么具体哪里出了问题呢? - David Medenjak
对于RecyclerView中的元素,偏移量显示正确(例如,最后一列中的元素左侧20像素和右侧40像素)。使用上述代码,由于我从方法返回position == RecyclerView.NO_POSITION,因此动画从没有边距的位置(0左侧和0右侧)开始。在这里,我需要找出删除元素的位置(例如,最后一列),以应用正确的偏移量以实现平滑动画。 - jeff_bordon
5个回答

6
尝试使用视图的LayoutParams。如果您没有使用自定义LayoutManager,那么它应该包含您所需的信息。
int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition();

非常感谢LayoutParams!一开始getViewAdapterPosition()不知道为什么不起作用,但现在我使用RecyclerView.LayoutParams.leftMargin等来应用边距,而不是调用outRect.set(left, top, right, bottom),动画效果如预期般流畅! - jeff_bordon
@jeff_bordon,你能解释一下你是如何做到的吗?我有个想法,但仍然存在一个问题,那就是边距(或偏移量)实际上被应用于错误的项目 - 不是当前已删除的项目,而是下一个项目,它仍在适配器中。 - Michał Klimczak
请注意,如果在“change”动画之前传递了adapterPosition,则它可能与Recycler的位置不同。 - Filipkowicz

5

实际上,从父级获取子级位置并不是一个好决定。因为在同一时间可能会有工作或者ItemDecorator、LayoutManager、RecyclerView来管理它们,或者Adapter更改元素的位置,所以实际状态与真实状态可能不同。

更好的方式是,向Recycler请求为特定视图提供ViewHolder。然后你可以获得你需要的所有信息(getLayoutPosition(),getAdapterPosition())。

parent.getChildViewHolder(view).getLayoutPosition();
parent.getChildViewHolder(view).getAdapterPosition;

还有一个建议,不是针对你的情况。如果你需要获取ItemCount,请不要使用adapter.getItemCount(),最好从state中获取,因为适配器和Recycler中的项数可能不同。

state.getItemCount()

4

我在这里遇到了同样的问题,阅读了答案和讨论。虽然可能有点晚,但我会回答这个问题,以便其他人解决这个问题。

Julian Os的答案应该标记为正确答案,但并没有太多解释。

以下是详细的说明:

ItemDecorationgetItemOffsets()中,首先获取项目位置parent.getChildViewHolder(view).getAdapterPosition();,正如ilyagorbunov所指出的,这是一种更安全的获取视图位置的方法。当查询的视图不存在时,此方法返回RecyclerView.NO_POSITION。因此,如果是这种情况,请检查parent.getChildViewHolder(view).getOldPosition(),该方法返回动画视图的旧位置。当ChildViewHolder不再存在于视图中或已完成动画时,此方法还会返回RecyclerView.NO_POSITION

如果您的适配器具有多个视图类型,请直接从parent.getChildViewHolder(view).getItemViewType()获取视图类型。

为了完整起见,以下是一些代码片段:

override fun getItemOffsets(outRect: Rect?, view: View?, parent: RecyclerView?, state: RecyclerView.State?) {
    super.getItemOffsets(outRect, view, parent, state)
    var position = parent?.getChildViewHolder(view)?.adapterPosition
    if (position == RecyclerView.NO_POSITION) {
        val oldPosition = parent?.getChildViewHolder(view)?.oldPosition
        if (oldPosition == RecyclerView.NO_POSITION) return
        position = oldPosition
    }

    val viewType = parent?.getChildViewHolder(view)?.itemViewType

    when (viewType) {
        //do your outRect here
    }
}

2

经过一段时间的尝试,我最终发现了ViewHolder#getOldPosition

文档说明如下:

当LayoutManager支持动画时,RecyclerView会跟踪3个位置的ViewHolders以执行动画。

如果ViewHolder在上一个onLayout调用中被布局,则旧位置将保留其在先前布局中的适配器索引。

换句话说,getOldPosition()返回ViewHolder在被移除之前所在的适配器位置。

因此,要从ItemDecoration#getItemOffsets方法内部获取此值,只需编写以下代码:

parent.getChildViewHolder(view).getOldPosition()

我亲自测试过,它确实有效 :)


0

你应该从以下代码中获取:

(view.layoutParams as? GridLayoutManager.LayoutParams)?.spanIndex

同样的,也是一样:

(view.layoutParams as? GridLayoutManager.LayoutParams)?.spanSize

这样做可以确保在动画期间安全,因为适配器可能有新的数据集,而将在不同的项目集上工作,而不是您计算偏移量的那些项目集。

在项装饰器中使用适配器不是一个好主意。尽量仅基于视图和回收器状态进行所有计算。您可以考虑通过视图标签或回收器状态中的资源传递一些数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接