RecyclerView StaggeredGridLayoutManager重新排序问题

时间:2015-01-06 14:17:26

标签: android android-recyclerview recycler-adapter

我试图在RecyclerView中显示三个(至少是我遇到问题的情况)项目,其中StaggeredGridLayoutManager有两列。第一项跨越两行。这是它的样子:

Correct initial behaviour

现在,我正在移动项目"项目2"到达顶点。这是我调用的代码,在适配器中(它是我为了演示我在一个更复杂的项目中遇到的问题而编写的一个示例):

private int findById(int id) {
    for (int i = 0; i < items.size(); ++i) {
        if (items.get(i).title.equals("Item " + id)) {
            return i;
        }
    }

    return -1;
}

// Moving the item "Item 2" with id = 2 and position = 0
public void moveItem(int id, int position) {
    final int idx = findById(id);
    final Item item = items.get(idx);

    if (position != idx) {
        items.remove(idx);
        items.add(position, item);
        notifyItemMoved(idx, position);
        //notifyDataSetChanged();
    }
}

之后,数组很好:[Item 2, Item 1, Item 3]。但是,这个观点还远远不够:

Layout issue

如果我触摸RecyclerView(如果没有足够的项目可以滚动,则足以触发过滚动效果),项目2向左移动,我希望首先看到它(一个很好的动画):

Expected result

正如您在代码中看到的那样,我尝试通过调用notifyItemMoved(idx, position)来替换notifyDataSetChanged()。它有效,但改变不是动画。

我写了一个完整的示例来证明这一点并将其放在GitHub上。它几乎是最小的(有移动项目和切换其跨越的选项)。

我不知道自己做错了什么。这是StaggeredGridLayoutManager的错误吗?我想避免使用notifyDataSetChanged(),因为我希望保持动画的一致性。


编辑:经过一番挖掘,没有必要使用完全跨越的项目来显示问题。我删除了全跨度。当我尝试将第2项移动到位置0时,它不移动:第1项在它之后移动,第3项在右边移动,所以我有:空单元格,第2项,新行,第1项,第3项。滚动后我仍然有正确的布局。

更有意思的是我没有GridLayoutManager的问题。我需要一个完整的项目,因此它不是解决方案,但我想它确实是StaggeredGridLayoutManager中的错误......

2 个答案:

答案 0 :(得分:12)

我没有完整的答案,但我可以指出你的解决方法和错误报告(我认为是相关的)。

更新布局以使其看起来像是您的第二个屏幕截图,是在调用invalidateSpanAssignments()后调用StaggeredGridLayoutManger(sglm)上的notifyItemMoved()。 “挑战”是如果你在nIM()之后立即调用它,它就不会运行。如果你将呼叫延迟几毫秒,它就会。因此,在您引用的MainActivity代码中,我已将您的sglm设为私有字段:

private StaggeredGridLayoutManager sglm;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    adapter = new Adapter();
    recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    sglm = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
    recyclerView.setLayoutManager(sglm);
    recyclerView.setItemAnimator(new DefaultItemAnimator());
    recyclerView.setAdapter(adapter);
}

在开关块中向下,在处理程序中引用它:

        case R.id.move_sec_top:
            adapter.moveItem(2, 0);
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    sglm.invalidateSpanAssignments();
                }
            }, 100);
            return true;

结果是你的动画仍在运行,布局最终会以你想要的方式结束。这是一个真正的kludge,但它确实有效。我相信这是我在以下链接中发现并报告的相同错误:

https://code.google.com/p/android/issues/detail?id=93156

虽然我的“症状”和所需的电话不同,但潜在的问题似乎是相同的。

祝你好运!

编辑:无需postdelayed ,只需发帖即可:

        case R.id.move_sec_top:
            adapter.moveItem(2, 0);
            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    sglm.invalidateSpanAssignments();
                }
            });
            return true;

我原来的理论是,在布局通过结束之前,通话被阻止了,但我相信情况并非如此。相反,我现在认为如果你立即调用invalidateSpanAssignments(),它实际上执行得太快(在布局更改完成之前)。因此,上面的帖子(没有延迟)只是将调用添加到渲染队列的末尾,它在布局之后发生。

答案 1 :(得分:1)

我做到了这一点。

StaggeredGridLayoutManager gaggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
gaggeredGridLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
recyclerView.setLayoutManager(gaggeredGridLayoutManager);

dataList = YourDataList (Your Code for Arraylist);

recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerAdapter = new DataAdapter(dataList, recyclerView);
recyclerView.setAdapter(recyclerAdapter);

// Magic line
recyclerView.addOnScrollListener(new ScrollListener());

为自定义 RecyclerView Scroll Listener 创建类。

private class ScrollListener extends RecyclerView.OnScrollListener {
   @Override
   public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        gaggeredGridLayoutManager.invalidateSpanAssignments();
   }
}

希望这会对你有所帮助。

相关问题