2つの列のあるRecyclerView
を含むStaggeredGridLayoutManager
に3つの項目(少なくとも問題がある場合)を表示しようとしています。最初のアイテムは2つの行にまたがっています。これは次のようになります。
次に、アイテム「アイテム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]
_。ただし、このビューは良好ではありません。
RecyclerView
をタッチすると(スクロールするのに十分なアイテムがない場合にオーバースクロール効果をトリガーするのに十分です)、アイテム2が左に移動します。
コードで見たかもしれませんが、私はnotifyItemMoved(idx, position)
をnotifyDataSetChanged()
の呼び出しに置き換えようとしました。動作しますが、変更はアニメーション化されません。
これを実証するための完全なサンプルを作成して GitHub に配置しました。それはほとんど最小限です(アイテムを移動してそのスパンを切り替えるオプションがあります)。
何が悪いのかわかりません。これはStaggeredGridLayoutManager
のバグですか?アニメーションに関して一貫性を保ちたいので、notifyDataSetChanged()
は避けたいと思います。
編集:掘り下げた後、問題を示すために完全にスパンされたアイテムは必要ありません。フルスパンを外しました。アイテム2を位置0に移動しようとすると、アイテムが移動しません:アイテム1がその後ろに移動し、アイテム3が右側に移動します。空のセル、アイテム2、新しい行、アイテム1、アイテム3です。スクロールした後も正しいレイアウトです。
さらに興味深いのは、GridLayoutManager
に関する問題がないことです。フルスパンのアイテムが必要なため、それは解決策ではありませんが、StaggeredGridLayoutManager
…のバグだと思います
完全な答えはありませんが、回避策とバグレポート(私は関係があると思います)の両方を紹介します。
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()
をすぐに呼び出すと、実際には実行が早すぎる(レイアウトの変更が完了する前に)と思います。したがって、上記の投稿(遅延なし)は、レイアウトの後に発生するレンダリングキューの最後に呼び出しを追加するだけです。
さて、私はこのようにしました。
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();
}
}
これがお役に立てば幸いです。