2015-01-06 4 views
14

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

+0

Я не знаю, является ли это ошибкой или нет, но я тоже это видел, вещи только движутся, когда вы прокручиваете и проверяется «стратегия разрыва». – tyczj

+0

@tyczj: 'GAP_HANDLING_NONE' избегает перемещаемых предметов, но я все еще получают неправильное расположение. 'GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS', вероятно, по умолчанию, во всяком случае, у меня такое же поведение, как и без указания чего-либо. –

+0

Да, я знаю, что просто говорю, что после прокрутки адаптер проверяет пробелы, а затем перемещает вещи в нужное место. – tyczj

ответ

8

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

Хитрость обновления макет так, что он выглядит как ваш второй скриншот, чтобы позвонить 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(), это действительно выполняется слишком рано (до того, как изменения макета завершились). Таким образом, сообщение выше (без задержки) просто добавляет вызов в конец очереди рендеринга, где это происходит после макета.

+0

Звучит разумное объяснение. Я посмотрю на это завтра, спасибо! –

+0

Ошибка была подтверждена Google: https://code.google.com/p/android/issues/detail?id=93711#c1 В то же время ваше обходное решение отлично работает, поэтому я соглашусь с этим. –

+0

Любые идеи о том, как анимировать аннулирование присвоения диапазона? – mato

0

Ну, я сделал это.

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

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

+0

Спасибо, но исправление было выпущено Google в библиотеках поддержки 22 и новее. Теперь он работает из коробки с кодом, который я написал в исходном вопросе. –

Смежные вопросы