在 RecyclerView 中配置 ItemTouchHelper 以使 Snackbar 被显示的另一个 Snackbar 解散
Posted
技术标签:
【中文标题】在 RecyclerView 中配置 ItemTouchHelper 以使 Snackbar 被显示的另一个 Snackbar 解散【英文标题】:Configure ItemTouchHelper in RecyclerView to work for Snackbar being dismissed by another Snackbar being displayed 【发布时间】:2020-09-12 14:38:59 【问题描述】:我的应用中有一个包含消息的 RecyclerView。在向右滑动时,我将其配置为删除消息,在向左滑动时,将其存档。下面是删除和归档相关的ItemTouchHelper部分的代码:
if (direction == ItemTouchHelper.LEFT)
donorMessagesAdapter.deleteMessages(position);
else if (direction == ItemTouchHelper.RIGHT)
donorMessagesAdapter.archiveMessages(position);
这里是 deleteMessages() 和 archiveMessages() 函数(它们几乎相同):
public void deleteMessages(final int position)
recentlyDeletedPeer = sortedPeers.get(position);
recentlyDeletedPeerPosition = position;
sortedPeers.remove(position);
notifyItemRemoved(position);
Snackbar snackbar = Snackbar.make(activity.findViewById(R.id.constraint_layout),
"Messages Deleted", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener()
@Override
public void onClick(View view)
sortedPeers.add(recentlyDeletedPeerPosition, recentlyDeletedPeer);
notifyItemInserted(recentlyDeletedPeerPosition);
);
snackbar.setActionTextColor(0xff0099ff);
snackbar.addCallback(new Snackbar.Callback()
@Override
public void onDismissed(Snackbar snackbar, int event)
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT || event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)
MainActivity.database.receivedMessagesDao().deleteAllReceivedMessagesFromSender(email, recentlyDeletedPeer);
MainActivity.database.sentMessagesDao().deleteAllSentMessagesFromReceiver(email, recentlyDeletedPeer);
);
snackbar.show();
public void archiveMessages(final int position)
recentlyArchivedPeer = sortedPeers.get(position);
recentlyArchivedPeerPosition = position;
sortedPeers.remove(position);
notifyItemRemoved(position);
Snackbar snackbar = Snackbar.make(activity.findViewById(R.id.constraint_layout),
"Messages archived", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener()
@Override
public void onClick(View view)
sortedPeers.add(recentlyArchivedPeerPosition, recentlyArchivedPeer);
notifyItemInserted(recentlyArchivedPeerPosition);
);
snackbar.setActionTextColor(0xff0099ff);
snackbar.addCallback(new Snackbar.Callback()
@Override
public void onDismissed(Snackbar snackbar, int event)
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT || event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)
MainActivity.database.receivedMessagesDao().archive(email, recentlyArchivedPeer);
MainActivity.database.sentMessagesDao().archive(email, recentlyArchivedPeer);
);
snackbar.show();
删除/归档一条消息时一切正常。在 Snackbar 消失后,滑动会删除或存档消息,当我单击撤消时,删除/存档会反转。删除/归档多条消息时会出现问题。
问题 1: 当我删除/归档多条消息而不等待每条消息的 Snackbar 消失时,除了第一条消息之外的所有消息都被删除/归档。让我感到奇怪的是,并不是只有最后一条消息被删除/归档,除了第一条消息之外的所有消息都被删除/归档。
问题2:当我删除/归档多条消息而不等待每条消息的Snackbar消失时,如果我按UNDO,即在Snackbar上删除最后一条消息/存档,应用程序崩溃。
顺便说一句,我正在使用 Room Library 将消息存储在数据库中(由 MainActivity.database 引用)。
我认为问题出在 Snackbar 的 addCallback() 函数中。我应该为event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE
和event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT
做一些不同的事情,但我不知道我应该做些什么不同的事情,以及究竟是什么导致了问题。
这是我的整个适配器类供参考(错误消息行旁边标有注释):
package com.example.treeapp;
import android.app.Activity;
import android.content.Intent;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar;
import com.mikhaellopez.circularimageview.CircularImageView;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class DonorMessagesAdapter extends RecyclerView.Adapter<DonorMessagesAdapter.DonorMessagesViewHolder> // line 24 in error message
private Activity activity;
public String email;
public List<String> sortedPeers;
public static CircularImageView imageView;
private String recentlyDeletedPeer;
private int recentlyDeletedPeerPosition;
private String recentlyArchivedPeer;
private int recentlyArchivedPeerPosition;
public static class DonorMessagesViewHolder extends RecyclerView.ViewHolder
public Button middleButton;
public TextView nameTextView;
public TextView messageTextView;
DonorMessagesViewHolder(View view)
super(view);
middleButton = view.findViewById(R.id.middle_button);
imageView = view.findViewById(R.id.image_view);
nameTextView = view.findViewById(R.id.text_view_name);
messageTextView = view.findViewById(R.id.text_view_message);
DonorMessagesAdapter(Activity activity, String email)
this.activity = activity;
this.email = email;
this.sortedPeers = getSortedPeers(email);
@NonNull
@Override
public DonorMessagesViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.message, parent, false);
return new DonorMessagesViewHolder(view);
@Override
public void onBindViewHolder(@NonNull DonorMessagesViewHolder holder, int position)
final String current = sortedPeers.get(position);
// Start of getting last message
String lastMessage;
String lastMessageType;
List<Integer> receivedMessages = MainActivity.database.receivedMessagesDao().getMessagesToReceiverFromSender(email, current, 0);
List<Integer> sentMessages = MainActivity.database.sentMessagesDao().getMessagesBySenderToReceiver(email, current, 0);
if (sentMessages.size() == 0)
lastMessage = MainActivity.database.receivedMessagesDao().getMessage(receivedMessages.get(receivedMessages.size() - 1)); // line 74 in error message
lastMessageType = "received";
if (MainActivity.database.receivedMessagesDao().getImage(receivedMessages.get(receivedMessages.size() - 1)) == 1)
lastMessageType = "receivedImage";
else if (receivedMessages.size() == 0)
lastMessage = MainActivity.database.sentMessagesDao().getMessage(sentMessages.get(sentMessages.size() - 1));
lastMessageType = "sent";
if (MainActivity.database.sentMessagesDao().getImage(sentMessages.get(sentMessages.size() - 1)) == 1)
lastMessageType = "sentImage";
else
LocalDateTime lastReceivedMessageTime = LocalDateTime.parse(MainActivity.database.receivedMessagesDao().getTime(receivedMessages.get(receivedMessages.size() - 1)));
LocalDateTime lastSentMessageTime = LocalDateTime.parse(MainActivity.database.sentMessagesDao().getTime(sentMessages.get(sentMessages.size() - 1)));
if (lastReceivedMessageTime.isAfter(lastSentMessageTime))
lastMessage = MainActivity.database.receivedMessagesDao().getMessage(receivedMessages.get(receivedMessages.size() - 1));
lastMessageType = "received";
if (MainActivity.database.receivedMessagesDao().getImage(receivedMessages.get(receivedMessages.size() - 1)) == 1)
lastMessageType = "receivedImage";
else
lastMessage = MainActivity.database.sentMessagesDao().getMessage(sentMessages.get(sentMessages.size() - 1));
lastMessageType = "sent";
if (MainActivity.database.sentMessagesDao().getImage(sentMessages.get(sentMessages.size() - 1)) == 1)
lastMessageType = "sentImage";
// End of getting last message
if (MainActivity.database.plantersDao().checkEmail(current) == 1)
if (MainActivity.database.plantersDao().getImageUrl(current) != null)
new DownloadImageTask(imageView).execute(MainActivity.database.plantersDao().getImageUrl(current));
else if (MainActivity.database.donorsDao().checkEmail(current) == 1)
if (MainActivity.database.donorsDao().getImageUrl(current) != null)
new DownloadImageTask(imageView).execute(MainActivity.database.donorsDao().getImageUrl(current));
if (MainActivity.database.plantersDao().checkEmail(current) == 1)
holder.nameTextView.setText(MainActivity.database.plantersDao().getName(current));
else if (MainActivity.database.donorsDao().checkEmail(current) == 1)
holder.nameTextView.setText(MainActivity.database.donorsDao().getName(current));
if (lastMessageType.equals("received"))
if (lastMessage.length() > 17)
holder.messageTextView.setText(lastMessage.substring(0, 16) + "...");
else
holder.messageTextView.setText(lastMessage);
else if (lastMessageType.equals("sent"))
if (lastMessage.length() > 12)
holder.messageTextView.setText("You: " + lastMessage.substring(0, 11) + "...");
else
holder.messageTextView.setText("You: " + lastMessage);
else if (lastMessageType.equals("receivedImage"))
holder.messageTextView.setText("Image");
else if (lastMessageType.equals("sentImage"))
holder.messageTextView.setText("You: " + "Image");
holder.middleButton.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View view)
Intent intent = new Intent(view.getContext(), MessageActivity.class);
intent.putExtra("email", email);
intent.putExtra("peerEmail", current);
intent.putExtra("archived", 0);
view.getContext().startActivity(intent);
);
@Override
public int getItemCount()
return sortedPeers.size();
public void reload()
sortedPeers = getSortedPeers(email);
notifyDataSetChanged();
private List<String> getSortedPeers(String email)
List<String> senders = MainActivity.database.receivedMessagesDao().getSendersForReceiver(email, 0);
List<String> receivers = MainActivity.database.sentMessagesDao().getReceiversForSender(email, 0);
List<String> peers = new ArrayList<>();
peers.addAll(senders);
peers.addAll(receivers);
peers = new ArrayList<>(new HashSet<>(peers));
List<String> sortedPeers = new ArrayList<>();
while (peers.size() > 0)
LocalDateTime latestMessageTime = null;
String latestPeer = null;
for (String peer : peers)
List<Integer> receivedMessages = MainActivity.database.receivedMessagesDao().getMessagesToReceiverFromSender(email, peer, 0);
List<Integer> sentMessages = MainActivity.database.sentMessagesDao().getMessagesBySenderToReceiver(email, peer, 0);
LocalDateTime lastMessageTime;
if (sentMessages.size() == 0)
lastMessageTime = LocalDateTime.parse(MainActivity.database.receivedMessagesDao().getTime(receivedMessages.get(receivedMessages.size() - 1)));
else if (receivedMessages.size() == 0)
lastMessageTime = LocalDateTime.parse(MainActivity.database.sentMessagesDao().getTime(sentMessages.get(sentMessages.size() - 1)));
else
LocalDateTime lastReceivedMessageTime = LocalDateTime.parse(MainActivity.database.receivedMessagesDao().getTime(receivedMessages.get(receivedMessages.size() - 1)));
LocalDateTime lastSentMessageTime = LocalDateTime.parse(MainActivity.database.sentMessagesDao().getTime(sentMessages.get(sentMessages.size() - 1)));
if (lastReceivedMessageTime.isAfter(lastSentMessageTime))
lastMessageTime = lastReceivedMessageTime;
else
lastMessageTime = lastSentMessageTime;
if (latestMessageTime != null)
if (lastMessageTime.isAfter(latestMessageTime))
latestMessageTime = lastMessageTime;
latestPeer = peer;
else
latestMessageTime = lastMessageTime;
latestPeer = peer;
sortedPeers.add(latestPeer);
peers.remove(latestPeer);
return sortedPeers;
public void deleteMessages(final int position)
recentlyDeletedPeer = sortedPeers.get(position);
recentlyDeletedPeerPosition = position;
sortedPeers.remove(position);
notifyItemRemoved(position);
Snackbar snackbar = Snackbar.make(activity.findViewById(R.id.constraint_layout),
"Messages Deleted", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener()
@Override
public void onClick(View view)
sortedPeers.add(recentlyDeletedPeerPosition, recentlyDeletedPeer);
notifyItemInserted(recentlyDeletedPeerPosition);
);
snackbar.setActionTextColor(0xff0099ff);
snackbar.addCallback(new Snackbar.Callback()
@Override
public void onDismissed(Snackbar snackbar, int event)
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT || event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)
MainActivity.database.receivedMessagesDao().deleteAllReceivedMessagesFromSender(email, recentlyDeletedPeer);
MainActivity.database.sentMessagesDao().deleteAllSentMessagesFromReceiver(email, recentlyDeletedPeer);
);
snackbar.show();
public void archiveMessages(final int position)
recentlyArchivedPeer = sortedPeers.get(position);
recentlyArchivedPeerPosition = position;
sortedPeers.remove(position);
notifyItemRemoved(position);
Snackbar snackbar = Snackbar.make(activity.findViewById(R.id.constraint_layout),
"Messages archived", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener()
@Override
public void onClick(View view)
sortedPeers.add(recentlyArchivedPeerPosition, recentlyArchivedPeer);
notifyItemInserted(recentlyArchivedPeerPosition);
);
snackbar.setActionTextColor(0xff0099ff);
snackbar.addCallback(new Snackbar.Callback()
@Override
public void onDismissed(Snackbar snackbar, int event)
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT || event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)
MainActivity.database.receivedMessagesDao().archive(email, recentlyArchivedPeer);
MainActivity.database.sentMessagesDao().archive(email, recentlyArchivedPeer);
reload();
);
snackbar.show();
这是我得到的错误(我的类标有**):
2020-09-12 19:24:11.547 689-689/com.example.treeapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.treeapp, PID: 689
java.lang.ArrayIndexOutOfBoundsException: length=0; index=-1
at java.util.ArrayList.get(ArrayList.java:439)
**at com.example.treeapp.DonorMessagesAdapter.onBindViewHolder(DonorMessagesAdapter.java:74)
at com.example.treeapp.DonorMessagesAdapter.onBindViewHolder(DonorMessagesAdapter.java:24)**
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7065)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7107)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6012)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6279)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1762)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:955)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3092)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2779)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1863)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8072)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
at android.view.Choreographer.doCallbacks(Choreographer.java:723)
at android.view.Choreographer.doFrame(Choreographer.java:658)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
【问题讨论】:
【参考方案1】:要解决您的问题 1,您可以尝试仅检查关闭是否是由点击引起的,例如:
snackbar.addCallback(new Snackbar.Callback()
@Override
public void onDismissed(Snackbar snackbar, int event)
if (event != Snackbar.Callback.DISMISS_EVENT_ACTION)
MainActivity.database.receivedMessagesDao().deleteAllReceivedMessagesFromSender(email, recentlyDeletedPeer);
MainActivity.database.sentMessagesDao().deleteAllSentMessagesFromReceiver(email, recentlyDeletedPeer);
);
至于您的第二个问题,发生崩溃是因为您只检查 sentMessages 列表是否为空
if (sentMessages.size() == 0)
lastMessage = MainActivity.database.receivedMessagesDao().getMessage(receivedMessages.get(receivedMessages.size() - 1)); // line 74 in error message
lastMessageType = "received";
if (MainActivity.database.receivedMessagesDao().getImage(receivedMessages.get(receivedMessages.size() - 1)) == 1)
lastMessageType = "receivedImage";
你在这里忘记做的是检查 reveivedMessages 是否也是空的。 例如:
if (sentMessages.size() == 0)
if(receivedMessages.size() != 0)
lastMessage = MainActivity.database.receivedMessagesDao().getMessage(receivedMessages.get(receivedMessages.size() - 1)); // line 74 in error message
lastMessageType = "received";
if (MainActivity.database.receivedMessagesDao().getImage(receivedMessages.get(receivedMessages.size() - 1)) == 1)
lastMessageType = "receivedImage";
发生的情况是您的 receivedMessages 列表也是空的,这意味着您尝试在位置 -1 获取项目,这显然不起作用。
即使这些技巧解决了您的问题,您也应该认真考虑如何访问数据库中的数据,因为您似乎在 UI 线程上执行所有调用,这是不可取的,因为它会阻塞 UI 线程。
【讨论】:
我实施了您的第一个建议,但没有任何影响。至于第二个,我已经实现了 getSortedPeers() 并且数据库是 sortedPeers 只有在它有接收消息或发送消息时才应该有一个对等点。换句话说,如果 sentMessages.size() = 0 和 receivedMessages.size() = 0,则变量不应位于 sortedPeers 中。感谢您的帮助。 好的,但是关于你的第二个问题,这肯定是发生了什么,因为 receivedMessages.size() 必须为 0 才能获得这个 OutOfBoundException。它在堆栈跟踪中说长度为 0,您尝试访问的索引为 -1以上是关于在 RecyclerView 中配置 ItemTouchHelper 以使 Snackbar 被显示的另一个 Snackbar 解散的主要内容,如果未能解决你的问题,请参考以下文章
无法在 Recyclerview 中显示图像 - 文本已成功显示
Kotlin中实现RecyclerView嵌套RecyclerView
单击后退按钮时,在 RecyclerView 中获得相同的项目位置