异步删除ListView/RecyclerView项的最佳实践

8
我有一个带有CursorLoader的ListView。用户可以打开ListView项(打开另一个Fragment)或删除项。所有数据库操作都是异步的,通常只需要一小部分时间。但从技术上讲,用户可能会在删除回调之前删除项目并打开该项目,从而导致错误。处理这个问题的最佳方法是什么?以下是我看到的选项。
  1. 假设AsyncTask总是足够快以避免出现问题
  2. 在UI线程上执行数据库操作
  3. 在AsyncTask之前使ListView失效(但这会导致UI中的闪烁)
  4. 在AsyncTask期间以某种方式阻止用户输入
编辑:最终我使用了RecyclerView,但在从数据库中删除项目之后才能调用adapter.notifyItemRemoved(itemPos)。
3个回答

6

你的最佳选择将是使用 RecyclerView。因为当你点击删除时,你可以调用adapter.notifyItemRemoved(itemPos)。这样列表项就会从你的RecyclerView中以动画方式被移除,你不需要担心删除操作的结果。

请参阅Android开发者教程创建列表和卡片:

当你有数据集合,并且这些元素基于用户操作或网络事件在运行时更改时,请使用RecyclerView小部件。

这是一个例子:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements View.OnClickListener, View.OnLongClickListener {

private ArrayList<String> mDataset;
private static Context sContext;

public MyAdapter(Context context, ArrayList<String> myDataset) {
    mDataset = myDataset;
    sContext = context;
}

@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,int viewType) {
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_text_view, parent, false);

    ViewHolder holder = new ViewHolder(v);
    holder.mNameTextView.setOnClickListener(MyAdapter.this);

    holder.mNameTextView.setTag(holder);

    return holder;
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {

    holder.mNameTextView.setText(mDataset.get(position));

}

@Override
public int getItemCount() {
    return mDataset.size();
}


@Override
public void onClick(View view) {
    ViewHolder holder = (ViewHolder) view.getTag();
    if (view.getId() == holder.mNameTextView.getId()) {
        **//Important !!!**
        notifyItemRemoved(getPosition());
        // Make your database operation also here
    }
}



public static class ViewHolder extends RecyclerView.ViewHolder {
    public TextView mNumberRowTextView;
    public TextView mNameTextView;


    public ViewHolder(View v) {
        super(v);

        mNameTextView = (TextView) v.findViewById(R.id.nameTextView);
    }
}

如果我正在使用游标,该怎么办?在获得新的游标之前,我无法从数据集中删除该项。 - cambunctious
你不需要等待新的客户。只需先从recyclerView中删除项目。NotifyItemRemoved接受位置作为参数。因此,只需传递单击删除按钮的位置。我的意思是,在获取新游标后,您不需要将notifyItemRemoved放在其后面。更改优先级并在单击删除按钮时首先调用notifyItemRemoved。NotifyItemRemived不查看您的数据,它只从列表中删除您的回收视图项。然后,您还可以从数据库中删除数据。 - Emin Ayar
我尝试过了,但它不起作用。文档非常清楚,notifyItemRemoved 是用来通知适配器数据集中的项已被删除的。 - cambunctious
好的,只是另一个想法,我假设您可以为数据库数据集创建POJO,并且您可以创建适配器以显示来自List<YourPOJO>的内容,通过这种方式,您可以轻松地从列表中删除数据并快速更新UI。我的意思是您的代码将如下所示; myList.remove(itemPos)notifyItemRemoved(itemPos)db.deleteRecord(itemPos) - Emin Ayar
RecyclerView 的解决方案必须是被接受的答案。当您处理项目列表时,notifyDataSetChanged() 和其他 notify 方法非常好用。 - Yasin Kaçmaz
显示剩余2条评论

3
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final View itemView = convertView != null ? convertView : /* inflate view */;
        itemView.findViewById(R.id.delete_button).setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        itemView.setEnabled(false);
                        // OR view.setClickable(false);
                    }
                });
        return itemView;
    }

你可以使用带有移除动画的RecyclerView。

build.gradle

compile 'com.android.support:recyclerview-v7:23.2.1'

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
</RelativeLayout>

my_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/value_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"/>

    <Button
        android:id="@+id/delete_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Delete"/>

</LinearLayout>

MainActivity.java

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private List<String> items = new ArrayList<>(
            Arrays.asList("first", "second", "third"));

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RecyclerView myRecyclerView = (RecyclerView)
                findViewById(R.id.my_recycler_view);
        myRecyclerView.setAdapter(new MyAdapter());
    }

    private class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new MyViewHolder(LayoutInflater.from(MainActivity.this)
                    .inflate(R.layout.my_item, parent, false));
        }

        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            holder.bind(position);
        }

        @Override
        public int getItemCount() {
            return items.size();
        }

        public class MyViewHolder extends RecyclerView.ViewHolder {

            private TextView valueTextView;
            private Button deleteButton;

            public MyViewHolder(View itemView) {
                super(itemView);
                valueTextView = (TextView) itemView.findViewById(R.id.value_text);
                deleteButton = (Button) itemView.findViewById(R.id.delete_button);
                deleteButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        int position = (int) v.getTag();
                        items.remove(position);
                        notifyItemRemoved(position);
                        notifyItemRangeChanged(position, items.size());
                    }
                });
            }

            public void bind(int position) {
                valueTextView.setText(items.get(position));
                deleteButton.setTag(position);
            }
        }
    }
}

禁用视图会假设该项未绑定到另一个视图,对吗?您能指出我应该查看RecyclerView中的关键方法吗?如果不会过于复杂,那看起来是个有趣的选项。 - cambunctious
这其实非常简单 :) 只需在我的答案中添加代码即可。 - Andrew

0
如果您的数据库查询未返回大量数据,则在UI线程上执行数据库操作没有问题。

这在我的情况下很可能是真的,但我不确定是否采用。数据量取决于用户创建的项目数量。随着应用程序的发展,数据量也可以轻松增加。 - cambunctious

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接