Повторная настройка RecyclerView StaggeredGridLayoutManager

Я пытаюсь отобразить три (по крайней мере, тот случай, когда у меня проблема), в 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 (достаточно, чтобы вызвать эффект overscroll, если не хватает элементов для прокрутки), пункт 2 перемещается влево, где я ожидал увидеть его в первую очередь (с приятной анимацией):

Expected result

Как вы могли видеть в коде, я попытался заменить notifyItemMoved(idx, position) на вызов notifyDataSetChanged(). Он работает, но изменение не анимируется.

Я написал полный образец, чтобы продемонстрировать это, и положил его на GitHub. Это почти минимально (есть опции для перемещения элемента и переключения их охвата).

Я не вижу, что я могу делать неправильно. Это ошибка с StaggeredGridLayoutManager? Я хотел бы избежать notifyDataSetChanged(), поскольку я хотел бы поддерживать последовательность анимаций.


Изменить: после некоторого копания нет необходимости в полностью настроенном элементе, чтобы показать проблему. Я удалил полный пролет. Когда я пытаюсь переместить элемент 2 в позицию 0, он не перемещается: пункт 1 идет за ним, а элемент 3 перемещается вправо, поэтому у меня: пустая ячейка, пункт 2, новая строка, пункт 1, пункт 3 У меня все еще есть правильный макет после прокрутки.

Интересно, что у меня нет проблемы с GridLayoutManager. Мне нужен элемент полного диапазона, поэтому это не решение, но я думаю, что это действительно ошибка в StaggeredGridLayoutManager...

Ответы

Ответ 1

У меня нет полного ответа, но я могу указать на обходной путь и отчет об ошибке (который, как мне кажется, связан).

Трюк для обновления макета, чтобы он выглядел как ваш второй снимок экрана, заключается в вызове 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

Хотя мой "симптом" и требуемый вызов были разными, основная проблема, похоже, идентична.

Удачи!

РЕДАКТИРОВАТЬ: нет необходимости отправлять сообщения, просто публикация сделает трюк:

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

Моя первоначальная теория заключалась в том, что вызов был заблокирован до завершения макета, но я считаю, что это не так. Вместо этого теперь я думаю, что если вы сразу вызовете invalidateSpanAssignments(), это действительно выполняется слишком рано (до того, как изменения макета завершились). Таким образом, сообщение выше (без задержки) просто добавляет вызов в конец очереди визуализации, где это происходит после макета.

Ответ 2

Хорошо, я сделал это.

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();
   }
}

Надеюсь, это поможет вам.