如何延迟(撤消)滑动以删除使用Room和LiveData的Recyclerview?

时间:2019-07-13 13:54:03

标签: android android-recyclerview android-room android-livedata

我使用以下示例作为指南nemanja-kovacevic/recycler-view-swipe-to-delete,在我的应用程序中实现了“滑动到删除”功能。最初,我使用的是简单的SQLite数据库类,并且一切正常。

在尝试更新我的应用程序以利用Android的Architecture Components Room和LiveData时,我遵循了Google的Room with a view Codelab。更新代码后,它似乎可以正常工作,并且只需滑动一下即可。但是,如果您在撤消延迟完成之前划过另一行,LiveData会更新适配器的列表的缓存副本,以便后续挂起的删除运行对象无法找到它们应该在列表中移动的项目(位置= -1),导致应用崩溃。

很多解释,这是适配器代码:

    public class DropsListAdapter extends RecyclerView.Adapter<DropsListAdapter.DropHolder> {
    private final static int PENDING_REMOVAL_TIMEOUT = 3000; // 3sec
    private final LayoutInflater inflater;
    private Context context;
    private Handler mHandler = new Handler();
    private HashMap<DeadDrop, Runnable> pendingRunnables = new HashMap<>();
    private List<DeadDrop> deadDrops;
    private List<DeadDrop> dropsPendingRemoval;

    DropsListAdapter(Context context) {
        inflater = LayoutInflater.from(context);
        this.context = context;
        this.dropsPendingRemoval = new ArrayList<>();
    }

    @NonNull
    @Override
    public DropHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new DropHolder(inflater.inflate(R.layout.drop_list_item, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull DropHolder holder, int position) {
        final DeadDrop deadDrop = deadDrops.get(position);
        if (dropsPendingRemoval.contains(deadDrop)) {
            holder.itemView.setBackgroundColor(Color.WHITE);
            holder.undoIt.setVisibility(View.VISIBLE);
            holder.rowWrapper.setVisibility(View.GONE);
        } else {
            holder.rowWrapper.setVisibility(View.VISIBLE);
            holder.latitude.setText(Converts.latitudeToSexaString(deadDrop.getLatitude()));
            holder.longitude.setText(Converts.longitudeToSexaString(deadDrop.getLongitude()));
            holder.undoIt.setVisibility(View.GONE);
        }
    }

    @Override
    public int getItemCount() {
        if (deadDrops != null)
            return deadDrops.size();
        else return 0;
    }

    void pendingRemoval(int position) {
        final DeadDrop mDeadDrop = deadDrops.get(position);
        if (!dropsPendingRemoval.contains(mDeadDrop)) {
            dropsPendingRemoval.add(mDeadDrop);
            notifyItemChanged(position);
            Runnable pendingRemovalRunnable = new Runnable() {
                @Override
                public void run() {
                    // Here is the problem. After the first item is removed,
                    // the next drop to remove is not found in the newly updated
                    // list of items (deadDrops).
                    int pos = deadDrops.indexOf(mDeadDrop);
                    remove(pos);
                }
            };
            mHandler.postDelayed(pendingRemovalRunnable, PENDING_REMOVAL_TIMEOUT);
            pendingRunnables.put(mDeadDrop, pendingRemovalRunnable);
        }
    }

    void setDeadDrops(List<DeadDrop> drops) {
        deadDrops = drops;
        notifyDataSetChanged();
    }

    private void remove(int position) {
        DeadDrop drop = deadDrops.get(position);
        dropsPendingRemoval.remove(drop);
        if (deadDrops.contains(drop)) {
            deadDrops.remove(position);
            notifyItemRemoved(position);
            ((DeadDropActivity) context).mDeadDropViewModel.delete(drop);
            notifyDataSetChanged();
        }
    }

    boolean isPendingRemoval(int position) {
        return dropsPendingRemoval.contains(deadDrops.get(position));
    }

    /**
     * Drops List View Holder class
     */
    protected class DropHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        LinearLayout rowWrapper;
        TextView latitude, longitude;
        ImageButton mapIt;
        Button undoIt;

        DropHolder(View itemView) {
            super(itemView);

            rowWrapper = itemView.findViewById(R.id.row_wrapper);
            latitude = itemView.findViewById(R.id.latitude_sexagesimal);
            longitude = itemView.findViewById(R.id.longitude_sexagesimal);
            mapIt = itemView.findViewById(R.id.button_map_it);
            undoIt = itemView.findViewById(R.id.undo_button);
            mapIt.setOnClickListener(this);
            undoIt.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            if (v.getId() == R.id.button_map_it) {

                String gUri = String.format(Locale.ENGLISH,
                        "https://www.google.com/maps/@%f,%f," + DeadDropActivity.GMAPS_CLOSE_ZOOM + "z",
                        deadDrops.get(getLayoutPosition()).getLatitude(),
                        deadDrops.get(getLayoutPosition()).getLongitude());

                Intent gIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(gUri));
                gIntent.setClassName("com.google.android.apps.maps",
                        "com.google.android.maps.MapsActivity");

                try {
                    context.startActivity(gIntent);

                } catch (ActivityNotFoundException ex) {
                    try {
                        String uri = String.format(Locale.ENGLISH, "geo:%f,%f?z=25",
                                deadDrops.get(getLayoutPosition()).getLatitude(),
                                deadDrops.get(getLayoutPosition()).getLongitude());
                        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
                        context.startActivity(intent);

                    } catch (ActivityNotFoundException innerEx) {
                        Toast.makeText(context, "Please install a maps application or browser.",
                                Toast.LENGTH_LONG).show();
                        innerEx.printStackTrace();
                    }
                }

                Toast.makeText(context, "Map Button clicked at " + getLayoutPosition(), Toast.LENGTH_SHORT).show();
            } else if (v.getId() == R.id.undo_button) {
                DeadDrop deadDrop = deadDrops.get(getLayoutPosition());
                // user wants to undo the removal, let's cancel the pending task
                // Cancelling still works without issue.
                Runnable pendingRemovalRunnable = pendingRunnables.get(deadDrop);
                pendingRunnables.remove(deadDrop);
                if (pendingRemovalRunnable != null)
                    mHandler.removeCallbacks(pendingRemovalRunnable);
                dropsPendingRemoval.remove(deadDrop);
                // this will rebind the row in "normal" state
                notifyItemChanged(deadDrops.indexOf(deadDrop));
                Log.d(TAG, TAG_CLASS + ".onClickUndo(" + getLayoutPosition() + ")");
            }
        }
    }

    /**
     * Utility class
     */
    public static class Converts {

        static String latitudeToSexaString(double latitude) {
            String latDir = (latitude < 0) ? "S" : "N";
            double lat = Math.abs(latitude);
            double s;
            int d, m;

            d = (int) lat;
            m = (int) ((lat - d) * 60);
            s = (((lat - d) * 60) - m) * 60;

            return String.format(Locale.ENGLISH, "%02d\u00B0", d) +
                    String.format(Locale.ENGLISH, "%02d\u0027", m) +
                    String.format(Locale.ENGLISH, "%02.1f\"", s) + latDir;
        }

        static String longitudeToSexaString(double longitude) {
            String lonDir = (longitude < 0) ? "W" : "E";
            double lon = Math.abs(longitude);
            double s;
            int d, m;

            d = (int) lon;
            m = (int) ((lon - d) * 60);
            s = (((lon - d) * 60) - m) * 60;

            return String.format(Locale.ENGLISH, "%02d\u00B0", d) +
                    String.format(Locale.ENGLISH, "%02d\u0027", m) +
                    String.format(Locale.ENGLISH, "%02.1f\"", s) + lonDir;
        }
    }
}

这里是logcat,它显示indexof返回-1,因为找不到该实例,但这是因为它是同一对象的新实例(ID相同,但是object.toString( )不同:

2019-07-14 02:29:17.890 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): 2
2019-07-14 02:29:17.890 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): {}
2019-07-14 02:29:17.890 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): ID|16
2019-07-14 02:29:20.896 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.[Runnable]run(): 2
    DropID = com.daweber.deaddrop.DeadDrop@d769128(ID|16)
    DropsList = [com.daweber.deaddrop.DeadDrop@50eec1a, com.daweber.deaddrop.DeadDrop@966a34b, com.daweber.deaddrop.DeadDrop@d769128]
2019-07-14 02:29:20.992 18618-18618/com.daweber.deaddrop D/daweber.DD: .DeadDropActivity.[Observer].onChanged(): [com.daweber.deaddrop.DeadDrop@9f16479, com.daweber.deaddrop.DeadDrop@6fad5be]
2019-07-14 02:29:37.286 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): 1
2019-07-14 02:29:37.287 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): {com.daweber.deaddrop.DeadDrop@d769128=com.daweber.deaddrop.DropsListAdapter$1@38e0d6c}
2019-07-14 02:29:37.287 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): ID|15
2019-07-14 02:29:37.766 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): 0
2019-07-14 02:29:37.766 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): {com.daweber.deaddrop.DeadDrop@6fad5be=com.daweber.deaddrop.DropsListAdapter$1@4049458, com.daweber.deaddrop.DeadDrop@d769128=com.daweber.deaddrop.DropsListAdapter$1@38e0d6c}
2019-07-14 02:29:37.767 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.pendingRemoval(): ID|4
2019-07-14 02:29:40.292 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.[Runnable]run(): 1
    DropID = com.daweber.deaddrop.DeadDrop@6fad5be(ID|15)
    DropsList = [com.daweber.deaddrop.DeadDrop@9f16479, com.daweber.deaddrop.DeadDrop@6fad5be]
2019-07-14 02:29:40.358 18618-18618/com.daweber.deaddrop D/daweber.DD: .DeadDropActivity.[Observer].onChanged(): [com.daweber.deaddrop.DeadDrop@c9d4e22]
2019-07-14 02:29:40.769 18618-18618/com.daweber.deaddrop D/daweber.DD: .DropListAdapter.[Runnable]run(): -1
    DropID = com.daweber.deaddrop.DeadDrop@9f16479(ID|4)
    DropsList = [com.daweber.deaddrop.DeadDrop@c9d4e22]
2019-07-14 02:30:02.153 18766-18766/com.daweber.deaddrop D/daweber.DD: .DeadDropActivity.[Observer].onChanged(): [com.daweber.deaddrop.DeadDrop@ff36b61]

所以,现在的问题是,如何修改此行

int pos = deadDrops.indexOf(mDeadDrop);

通过查找object.getId()而不是对象签名来获取对象的索引?

1 个答案:

答案 0 :(得分:0)

因此,我找到了一种“蛮力”方法来执行此操作,它似乎可以防止崩溃,但是,如果列表增加到1000个条目,这可能不是最有效的方法,因此,如果有人有更好的解决方案:

新运行()

@Override
                public void run() {
                    final String TAG_FUN = ".[Runnable]run(): ";
                    // TODO: Here is the problem.
                    for (int i = 0; i <= deadDrops.size(); i++) {
                        DeadDrop d = deadDrops.get(i);
                        if (d.getId() == mDeadDrop.getId()) {
                            int pos = deadDrops.indexOf(d);
                            Log.d(TAG, TAG_CLS + TAG_FUN + pos
                                    + "\nDropID = " + d.toString() + "(ID|" + d.getId() + ")"
                                    + "\nDropsList = " + deadDrops.toString());
                            remove(pos);
                            break;
                        }
                    }
                }