Проблема переупорядочивания RecyclerView StaggeredGridLayoutManager
Я пытаюсь отобразить три (по крайней мере, в этом случае у меня проблема) элемента в RecyclerView
С StaggeredGridLayoutManager
С двумя колонками. Первый элемент распределен по двум рядам. Вот как это выглядит:
теперь я перемещаю элемент "пункт 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
(достаточно, чтобы вызвать эффект overscroll, если недостаточно элементов для прокрутки), пункт 2 переместите влево, где я ожидал увидеть его в первую очередь (с хорошей анимацией):
как вы, возможно, видели в коде, я попытался заменить notifyItemMoved(idx, position)
вызов notifyDataSetChanged()
. Это работает, но изменение не анимировано.
Я написал Полный образец, чтобы продемонстрировать это и поставить его на GitHub. Это почти минимально (есть варианты, чтобы переместить элемент и переключить их охват).
Я не вижу, что я могу делать неправильно. Это ошибка с StaggeredGridLayoutManager
? Я хотел бы избежать notifyDataSetChanged()
как я хотел бы сохранить последовательность в отношении анимации.
Edit: после некоторого рытья нет необходимости в полностью развернутом элементе, чтобы показать проблему. Я снял полный пролет. Когда я пытаюсь переместить пункт 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);
}
и вниз в блоке коммутатора, ссылаться на него в обработчике:
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
в то время как мой "симптом" и требуемый вызов были разные, основной вопрос, кажется, идентичен.
удачи!
EDIT: нет необходимости 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()
немедленно, он на самом деле выполняется слишком рано (до завершения изменений макета). Итак, сообщение выше (без delay) просто добавляет вызов в конец очереди рендеринга, где это происходит после макета.
Ну, я сделал так.
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());
создать класс для Custom RecyclerView Scroll Listener.
private class ScrollListener extends RecyclerView.OnScrollListener {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
gaggeredGridLayoutManager.invalidateSpanAssignments();
}
}
надеюсь, это поможет вам.