在 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_CONSECUTIVEevent == 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 中获得相同的项目位置

如何在recyclerView中管理点击事件

RecyclerView 与 Paging Library 和 PositionalDataSource 保持为空

无法将数据发送到 recyclerview