Я пытаюсь реализовать функцию перетаскивания для просмотра регенератора firebase. Недостаточно информации в документах для этой реализации. Я предполагаю, что мне нужно использовать onchildmoved для прослушивателя событий, но я не знаю, как изменить порядок данных.
Как использовать перетаскивание на просмотр recycler с использованием базы данных реального времени firebase
Ответ 1
У меня была проблема, подобная этому, и сделали следующее (предполагая, что вы используете класс, который расширяет ItemTouchHelper.SimpleCallback, например RecyclerViewTouchHelper):
1) Добавьте свойство к вашему потомку Firebase, чтобы записать его порядок в списке (например, "orderNumber" ).
2) Измените метод onMove() вашего RecyclerViewTouchHelper, чтобы включить следующее:
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
final int firstPosition = viewHolder.getAdapterPosition();;
final int secondPosition = target.getAdapterPosition();
DatabaseReference firstItemRef = mMovieAdapter.getRef(viewHolder.getAdapterPosition());
DatabaseReference secondItemRef = mMovieAdapter.getRef(target.getAdapterPosition());
HashMap<String, Object> updateFirstItemOrderNumber = new HashMap<>();
updateFirstItemOrderNumber.put("orderNumber", secondPosition);
firstItemRef.updateChildren(updateFirstItemOrderNumber);
HashMap<String, Object> updateSecondItemOrderNumber = new HashMap<>();
updateSecondItemOrderNumber.put("orderNumber", firstPosition);
secondItemRef.updateChildren(updateSecondItemOrderNumber);
return false;
}
3) При создании адаптера RecyclerViewAdapter убедитесь, что используемый вами запрос упорядочен по номеру порядка, например.
Query orderedListQuery = FirebaseRef.orderByChild("orderNumber");
Надеюсь, это поможет!
Ответ 2
У меня была довольно путаница, пытаясь заставить это работать для себя в Firebase UI 3.1.0. Здесь мое окончательное рабочее решение (объяснение ниже):
// overrides from ItemTouchHelper
override fun onMove(recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder): Boolean {
val fromPos = viewHolder.adapterPosition
val toPos = target.adapterPosition
val fromSnapshot = snapshots.first { it.order == fromPos }
val toSnapshot = snapshots.first { it.order == toPos }
Logger.d("Habit ${fromSnapshot.name} (${fromSnapshot.order}) to $toPos")
Logger.d("Habit ${toSnapshot.name} (${toSnapshot.order}) to $fromPos")
fromSnapshot.order = toPos
toSnapshot.order = fromPos
hasDragged = true
notifyItemMoved(toPos, fromPos)
return true
}
// overrides from FirebaseRecyclerAdapter
override fun onChildChanged(type: ChangeEventType?, snapshot: DataSnapshot?, newIndex: Int, oldIndex: Int) {
when (type) {
ChangeEventType.MOVED -> if (!hasDragged) {
super.onChildChanged(type, snapshot, newIndex, oldIndex)
}
else -> super.onChildChanged(type, snapshot, newIndex, oldIndex)
}
}
override fun onDataChanged() {
hasDragged = false
}
Я могу делать что-то неправильно, но казалось, что этот ответ выше выше дублирующегося кода, так как FirebaseRecyclerAdapter и наблюдаемые моментальные снимки будут автоматически обновляться при уведомлении. Это привело к тому, что сопротивление резко прекратилось с новыми значениями. Я также получал неправильные номера заказов (возможно, моя ошибка) с помощью getSnapshots(). Get (pos) (моментальные снимки [fromPos] в kotlin). Это вызвало некоторые действительно странные анимации. Также, когда я уведомлялся в движении, мне пришлось перевернуть позиции to/from (target/viewHolder). Однако учтите, что это зависит от заказа в запросе, используя поле заказа. И, наконец, я не хочу, чтобы метод FirebaseRecyclerAdapter # onChildChanged вызывал, если он из-за перетаскивания пользователя, что также вызывает нежелательную анимацию и дублирование усилий.
Ответ 3
Здесь я отправляю решение, которое сработало для меня. Он основан на ответе @prodaea, который имеет правильный подход, но не полностью функциональный. В нем отсутствует обновление базы данных firebase и дополнительная проверка с помощью ChangeEventType.CHANGED, чтобы избежать прерывания анимации при обновлении firebase.
Код живет внутри адаптера, который расширяет FirebaseRcyclerAdapter
и реализует интерфейс с помощью одного метода onItemMove(fromPosition: Int, toPosition: Int): Boolean
:
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
snapshots[fromPosition].sort = toPosition.toDouble()
snapshots[toPosition].sort = fromPosition.toDouble()
// update recycler locally to complete the animation
notifyItemMoved(fromPosition, toPosition)
updateFirebase(fromPosition, toPosition)
return true
}
private fun updateFirebase(fromPos: Int, toPos: Int) {
// flag which prevents callbacks from interrupting the drag animation when firebase database updates
hasDragged = true
val firstPath = getRef(fromPos).getPath()
val secondPath = getRef(toPos).getPath()
Log.d(_tag, "onItemMove: firstPath: $firstPath, secondPath: $secondPath")
val updates = mapOf(
Pair(firstPath + "/sort", snapshots[fromPos].sort),
Pair(secondPath + "/sort", snapshots[toPos].sort))
getRef(fromPos).root.updateChildren(updates)
Log.d(_tag, "updateFirebase, catA: \"${snapshots[fromPos]}\", catB: \"${snapshots[toPos]}\"")
}
private fun DatabaseReference.getPath() = toString().substring(root.toString().length)
override fun onChildChanged(type: ChangeEventType,
snapshot: DataSnapshot,
newIndex: Int,
oldIndex: Int) {
Log.d(_tag, "onChildChanged:else, type:$type, new: $newIndex, old: $oldIndex, hasDragged: $hasDragged, snapshot: $snapshot")
when (type) {
// avoid the drag animation interruption by checking against 'hasDragged' flag
ChangeEventType.MOVED -> if (!hasDragged) {
super.onChildChanged(type, snapshot, newIndex, oldIndex)
}
ChangeEventType.CHANGED -> if (!hasDragged) {
super.onChildChanged(type, snapshot, newIndex, oldIndex)
}
else -> {
super.onChildChanged(type, snapshot, newIndex, oldIndex)
}
}
}
override fun onDataChanged() {
hasDragged = false
Log.d(_tag, "onDataChanged, hasDragged: $hasDragged")
}