如何使用firebase实时数据库在回收视图上拖放

时间:2022-02-26 13:17:51

Im trying to implement the drag and drop feature to firebase recycler view. There is not enough information in the docs for this implementation. Im assuming i have to use onchildmoved for the event listener but i do not know how to reorder the data.

我正在尝试实现拖放功能到消防基地回收视图。在文档中没有足够的信息用于这个实现。我假设我必须为事件监听器使用onchildmove,但是我不知道如何重新排列数据。

3 个解决方案

#1


1  

I had an issue similar to this and have done the following (assuming you are using a class which extends ItemTouchHelper.SimpleCallback e.g. RecyclerViewTouchHelper):

我遇到了类似的问题,并做了以下工作(假设您正在使用扩展ItemTouchHelper的类)。SimpleCallback例如RecyclerViewTouchHelper):

1) Add a property to your Firebase child to record its order in the list (e.g. "orderNumber").

1)将一个属性添加到您的Firebase子中,以记录它在列表中的顺序(例如:“orderNumber”)。

2) Modify the onMove() method of your RecyclerViewTouchHelper to include the following:

2)修改您的recycling的onMove()方法,包括以下内容:

@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) When creating your RecyclerViewAdapter, make sure that the query you use is ordered by ordernumber e.g.

3)在创建可回收的viewadapter时,请确保您使用的查询是按ordernumber排序的。

Query orderedListQuery =  FirebaseRef.orderByChild("orderNumber");

Hope this helps!

希望这可以帮助!

#2


1  

I had quite a bit of confusion trying to get this to work for myself on Firebase UI 3.1.0. Here's my final working solution (explanation below):

在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
}

I may be doing something wrong, but it seemed that this answer above above code duplicated effort since the FirebaseRecyclerAdapter and the observable snapshots will automagically update on notification. This caused the drag to end abruptly with new values. I was also getting wrong order numbers (probably my fault) by using getSnapshots().get(pos) (snapshots[fromPos] in kotlin). This caused some really weird animations. Also when notifying on the move, I had to reverse the to/from (target/viewHolder) positions. However, please note this does rely on ordering in the query using the order field. And finally, I don't want to let the FirebaseRecyclerAdapter#onChildChanged method to call if it's because of a user drag, that also causes unwanted animation and duplicated effort.

我可能做错了什么,但是上面的答案似乎是重复的代码,因为FirebaseRecyclerAdapter和可观察快照将自动更新通知。这导致拖拽以新的值突然结束。使用getsnapshot ().get(pos) (kotlin中的快照[fromPos])也得到了错误的订单号(可能是我的错)。这就产生了一些奇怪的动画。同时,当我通知移动的时候,我也必须改变到/从(目标/维埃夫尔)位置。但是,请注意,这确实依赖于使用order字段在查询中排序。最后,我不想让FirebaseRecyclerAdapter#onChildChanged方法调用,如果它是由于用户的拖拽,也会导致不需要的动画和重复的工作。

#3


1  

Here I post solution that worked for me. It's based on @prodaea's answer, which has right approach but is not fully functional. It's missing firebase database update and additional check against ChangeEventType.CHANGED to avoid the animation interruption when firebase updates.

在这里,我发布了对我有用的解决方案。它是基于@prodaea的答案,它有正确的方法,但是没有完全的功能。它丢失了firebase数据库更新和针对ChangeEventType的附加检查。更改以避免在firebase更新时动画中断。

Code lives inside adapter which extends FirebaseRcyclerAdapter and implements interface with a single method onItemMove(fromPosition: Int, toPosition: Int): Boolean:

代码位于适配器内部,该适配器扩展了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")
}

#1


1  

I had an issue similar to this and have done the following (assuming you are using a class which extends ItemTouchHelper.SimpleCallback e.g. RecyclerViewTouchHelper):

我遇到了类似的问题,并做了以下工作(假设您正在使用扩展ItemTouchHelper的类)。SimpleCallback例如RecyclerViewTouchHelper):

1) Add a property to your Firebase child to record its order in the list (e.g. "orderNumber").

1)将一个属性添加到您的Firebase子中,以记录它在列表中的顺序(例如:“orderNumber”)。

2) Modify the onMove() method of your RecyclerViewTouchHelper to include the following:

2)修改您的recycling的onMove()方法,包括以下内容:

@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) When creating your RecyclerViewAdapter, make sure that the query you use is ordered by ordernumber e.g.

3)在创建可回收的viewadapter时,请确保您使用的查询是按ordernumber排序的。

Query orderedListQuery =  FirebaseRef.orderByChild("orderNumber");

Hope this helps!

希望这可以帮助!

#2


1  

I had quite a bit of confusion trying to get this to work for myself on Firebase UI 3.1.0. Here's my final working solution (explanation below):

在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
}

I may be doing something wrong, but it seemed that this answer above above code duplicated effort since the FirebaseRecyclerAdapter and the observable snapshots will automagically update on notification. This caused the drag to end abruptly with new values. I was also getting wrong order numbers (probably my fault) by using getSnapshots().get(pos) (snapshots[fromPos] in kotlin). This caused some really weird animations. Also when notifying on the move, I had to reverse the to/from (target/viewHolder) positions. However, please note this does rely on ordering in the query using the order field. And finally, I don't want to let the FirebaseRecyclerAdapter#onChildChanged method to call if it's because of a user drag, that also causes unwanted animation and duplicated effort.

我可能做错了什么,但是上面的答案似乎是重复的代码,因为FirebaseRecyclerAdapter和可观察快照将自动更新通知。这导致拖拽以新的值突然结束。使用getsnapshot ().get(pos) (kotlin中的快照[fromPos])也得到了错误的订单号(可能是我的错)。这就产生了一些奇怪的动画。同时,当我通知移动的时候,我也必须改变到/从(目标/维埃夫尔)位置。但是,请注意,这确实依赖于使用order字段在查询中排序。最后,我不想让FirebaseRecyclerAdapter#onChildChanged方法调用,如果它是由于用户的拖拽,也会导致不需要的动画和重复的工作。

#3


1  

Here I post solution that worked for me. It's based on @prodaea's answer, which has right approach but is not fully functional. It's missing firebase database update and additional check against ChangeEventType.CHANGED to avoid the animation interruption when firebase updates.

在这里,我发布了对我有用的解决方案。它是基于@prodaea的答案,它有正确的方法,但是没有完全的功能。它丢失了firebase数据库更新和针对ChangeEventType的附加检查。更改以避免在firebase更新时动画中断。

Code lives inside adapter which extends FirebaseRcyclerAdapter and implements interface with a single method onItemMove(fromPosition: Int, toPosition: Int): Boolean:

代码位于适配器内部,该适配器扩展了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")
}