web-dev-qa-db-ja.com

RecyclerView StaggeredGridLayoutManagerの並べ替えの問題

2つの列のあるRecyclerViewを含むStaggeredGridLayoutManagerに3つの項目(少なくとも問題がある場合)を表示しようとしています。最初のアイテムは2つの行にまたがっています。これは次のようになります。

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…のバグだと思います

16

完全な答えはありませんが、回避策とバグレポート(私は関係があると思います)の両方を紹介します。

2番目のスクリーンショットのようにレイアウトを更新するコツは、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);
}
_

そして、switchブロックで、ハンドラーで参照します。

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

その結果、アニメーションは引き続き実行され、レイアウトは希望どおりに仕上がります。これは本当のクラッジですが、機能します。これは、次のリンクで見つけて報告したのと同じバグだと思います。

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()をすぐに呼び出すと、実際には実行が早すぎる(レイアウトの変更が完了する前に)と思います。したがって、上記の投稿(遅延なし)は、レイアウトの後に発生するレンダリングキューの最後に呼び出しを追加するだけです。

13
MojoTosh

さて、私はこのようにしました。

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スクロールリスナー

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

これがお役に立てば幸いです。

1
Hiren Patel