Android:在 RecyclerView 中拖放项目时如何有效更新 SQLite 数据库?

Posted

技术标签:

【中文标题】Android:在 RecyclerView 中拖放项目时如何有效更新 SQLite 数据库?【英文标题】:Android: How do I effectively update the SQLite database when dragging and dropping items in the RecyclerView? 【发布时间】:2018-07-26 23:35:16 【问题描述】:

我有这个待办事项列表应用程序,其中“任务”可以包含他们自己的“子任务”列表。问题是,每当这些“任务”通过拖放重新排列时,有时会出现奇怪的行为,例如单击它们以显示其子列表时崩溃。

这是拖放监听器的代码:

private void attachItemTouchHelperToAdapter() 
    ItemTouchHelper.SimpleCallback touchCallback =
            new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
                    ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) 
                @Override
                public boolean onMove(RecyclerView recyclerView,
                                      RecyclerView.ViewHolder viewHolder,
                                      RecyclerView.ViewHolder target) 
                    final int fromPosition = viewHolder.getAdapterPosition();
                    final int toPosition = target.getAdapterPosition();
                    rearrangeTasks(fromPosition, toPosition);

                    // Call background thread to update database.
                    new UpdateDatabaseTask(mTaskList).execute();
                    return true;
                

                @Override
                public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) 
                    int position = viewHolder.getAdapterPosition();
                    Task taskToDelete = mTaskAdapter.mTasks.get(position);
                    TaskLab.get(getActivity()).deleteTask(taskToDelete);
                    updateUI(position);
                
            ;

    ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchCallback);
    itemTouchHelper.attachToRecyclerView(mTaskRecyclerView);

重新排列在 onMove 中调用的列表的代码:

private void rearrangeTasks(int fromPosition, int toPosition) 
    if (fromPosition < toPosition) 
        for (int i = fromPosition; i < toPosition; i++) 
            Collections.swap(mTaskList, i, i + 1);
        
     else 
        for (int i = fromPosition; i > toPosition; i--) 
            Collections.swap(mTaskList, i, i - 1);
        
    
    mTaskAdapter.notifyItemMoved(fromPosition, toPosition);

调用更新sqlite数据库的AsyncTask

 private class UpdateDatabaseTask extends AsyncTask<Void, Void, Void> 
    private List<Task> mTaskList;

    public UpdateDatabaseTask(List<Task> taskList) 
        mTaskList = taskList;
    

    @Override
    protected Void doInBackground(Void... voids) 
        TaskLab.get(getActivity()).updateDatabase(mTaskList);
        return null;
    

    @Override
    protected void onPostExecute(Void aVoid) 

    

调用数据库辅助函数以使用重新排列的列表更新整个表:

public void updateDatabase(List<Task> tasks) 
    mDatabase.delete(TaskTable.NAME, null, null);
    for (Task task : tasks) 
        addTask(task);
    

这里的问题是,有时,当快速拖放“任务”时,AsyncTask 更新数据库的速度不够快,因此当我尝试在它完成更新之前对其执行功能时,会出现奇怪的行为。

RecyclerView 拖放时更新 SQLite 数据库的更好方法是什么?

【问题讨论】:

【参考方案1】:

这个问题太宽泛,无法回答,因为您可以考虑多种解决方案来解决您的问题。我现在可以想到两个,在这里我将简要描述它们。

解决方案 #1

您可以考虑直接调用更新表的数据库方法,而不是通过AsyncTask 调用它。在您的数据库表中注册一个内容观察者,以便每次更新表时,RecyclerView 都会随之更新,而无需调用 notifyDataSetChanged()。示例实现应如下所示。

像这样声明数据库表的 URI。

public static final Uri DB_TABLE_TASKS_URI = Uri
            .parse("sqlite://" + Constants.ApplicationPackage + "/" + DB_TABLE_TASKS);

现在在您的updateDatabase 函数中,当您的任务表发生更改或更新时,您需要调用此通知函数来通知监听此内容的观察者。

public static void updateDatabase(List<Task> taskList) 
    // .. Your other implementation goes here 
    context.getContentResolver().notifyChange(DBConstants.DB_TABLE_TASKS_URI, null);

现在使用LoaderCallbacksRecyclerView 中显示sqlite 表中的任务。

public class TasksActivity extends Activity implements LoaderManager.LoaderCallbacks<Cursor> 
    private final TAKS_QUERY_LOADER = 0;

    protected void onCreate(Bundle savedInstanceState) 
        // Other code. 

        // Get your cursor loader initialized here. 
        getLoaderManager().initLoader(TASKS_QUERY_LOADER, null, this).forceLoad();
    

现在您需要在您的TasksActivity 中实现您的LoaderCallbacks 的功能。一个示例实现应如下所示。

@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) 

    return new SQLiteCursorLoader(getActivity()) 
        @Override
        public Cursor loadInBackground() 

            Cursor cursor;
            cursor = TasksLab.getTasks();

            // Register the cursor with the content observer
            this.registerContentObserver(cursor, DBConstants.DB_TABLE_TASKS_URI);
            return cursor;
        
    ;


@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) 
    // Set the cursor in the adapter of your RecyclerView here. 


@Override
public void onLoaderReset(Loader<Cursor> loader) 



// Do not forget to destroy the loader when you are destroying the view.
@Override
public void onDestroy() 
    getLoaderManager().destroyLoader(TASKS_QUERY_LOADER);
    super.onDestroyView();

就是这样。您现在可以在您的 sqlite 表中立即更新您的任务,并立即在您的RecyclerView 中获得反映。我强烈建议您一次更新一个任务。如我所见,您正在使用updateDatabase 函数在此处传递整个任务列表来更新数据库表。我建议只传递被拖动或更新的任务 ID,以便表格立即更新。

解决方案 #2

此解决方案比前一个解决方案更简单。这个想法是在刷新列表或销毁视图时更新您的 sqlite 数据库。

当一个任务被拖到某个地方(即更新其位置)或删除时,您需要将轨道保存在您的TasksActivity 中的某个ArrayList 中。不要立即更新数据库中的 sqlite 表,也不要使用 AsyncTask。当您在 onDestroyonPause 方法中销毁视图时,请保存此任务以备后用。在此过程中,每次您的Activity 恢复时(即在onResume 函数中),您都需要刷新您的RecyclerView。所以这里是伪实现。

@Override
public boolean onMove(RecyclerView recyclerView,
                      RecyclerView.ViewHolder viewHolder,
                      RecyclerView.ViewHolder target) 
    final int fromPosition = viewHolder.getAdapterPosition();
    final int toPosition = target.getAdapterPosition();
    rearrangeTasks(fromPosition, toPosition);

    // Do not call background thread to update database.
    // new UpdateDatabaseTask(mTaskList).execute();

    // Keep a local ArrayList and keep the track of dragging the item. 
    updateLocalArrayListWhichIsBindToRecyclerView(mTaskList);
    return true;


@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) 
    int position = viewHolder.getAdapterPosition();
    Task taskToDelete = mTaskAdapter.mTasks.get(position);
    // TaskLab.get(getActivity()).deleteTask(taskToDelete);
    updateLocalArrayListWhichIsBindedToRecyclerView(taskToDelete);
    updateUI(position);

现在,当您离开TaskActivity 时,您最终需要将更改保存在数据库中。

@Override
public void onDestroy() 
    updateDatabaseFromTheList();
    super.onDestroyView();


@Override
public void onPause() 
    updateDatabaseFromTheList();
    super.onDestroyView();

并且每次从onResume 函数内的数据库中加载任务。

@Override
public void onResume() 
    super.onResume();
    updateRecyclerViewFromDatabase();

更新

基于上述评论中解决方案#1 所需的说明。

“我建议只传递被拖动的任务 ID 或 更新,以便表格立即更新。 ”。我到底该怎么做 这样做?

我不确定您现在是如何更新任务表的。但是,我可以想到自己的一些实现。只需传递要更新其在任务表中的位置的任务。所以伪实现可能如下所示。

public class Task 
    public int taskId;
    public int taskPosition; 


public void updateDatabase(Task taskToBeUpdated, int newPosition) 

     // You need two update queries. 
     // Update task_table set task_position += task_position + 1 where task_position >= newPosition;
     // Update task_table set task_position = newPosition where taskId = taskTobeUpdated.taskId

希望对您有所帮助。

【讨论】:

感谢您付出了这么多努力来回答这个问题。您说过,“我建议只传递被拖动或更新的任务 ID,以便表格立即更新。”。我该怎么做?

以上是关于Android:在 RecyclerView 中拖放项目时如何有效更新 SQLite 数据库?的主要内容,如果未能解决你的问题,请参考以下文章

在 Recyclerview 之外拖放视图

在android中拖放textview

Android GridView中拖拽图片的问题

在“ListView”中拖放

Android RecyclerView点击事件处理

Android:在recyclerview下方显示按钮,但始终在屏幕上