为什么当更新数据库 sqlite 使用内容提供程序时,recyclerview 中的项目位置会发生变化?

Why item position in recyclerview change when update database sqlite use content provider?

我正在使用 recyclerview 中的 cursorloader 更新我的 sqlite 数据库,我通过选中复选框来更新我的 sqlite 数据库,更新了值但不知何故视图变得一团糟。有些行也被检查,但值仍然为 0,不应检查。当我插入新行时也会发生这种情况。

这是我更新数据库的 MainActivity

public class MainActivity extends AppCompatActivity implements
    TaskAdapter.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor>{

private TaskAdapter mAdapter;
private FloatingActionButton floatingActionButton;

private ArrayList<Task> task = new ArrayList<>();

private static final int TASKS_LOADER_ID = 0;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    mAdapter = new TaskAdapter(this);
    mAdapter.setOnItemClickListener(this);

    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    recyclerView.setHasFixedSize(true);
    recyclerView.setAdapter(mAdapter);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));

    floatingActionButton = (FloatingActionButton) findViewById(R.id.fab);


    /* Click events in floating action button */
    floatingActionButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent(getApplicationContext(), AddTaskActivity.class);
            startActivity(intent);
        }
    });

    //initialize the loader
    getSupportLoaderManager().initLoader(TASKS_LOADER_ID, null, this);
}

@Override
protected void onResume() {
    super.onResume();
    Log.v("dipanggil", "dipanggil jka");
    // re-queries for all tasks
    getSupportLoaderManager().restartLoader(TASKS_LOADER_ID, null, this);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();
    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings) {
        Intent intent = new Intent(this, SettingsActivity.class);
        startActivity(intent);
        return true;
    }
    return super.onOptionsItemSelected(item);
}

/* Click events in RecyclerView items */
@Override
public void onItemClick(View v, int position) {
    //TODO: Handle list item click event
    Intent intent = new Intent(this, TaskDetailActivity.class);
    /*b.putLong("ID", task.get(position).getId());
    b.putString("DESCRIPTION", task.get(position).getDescription());
    b.putInt("PRIORITY", task.get(position).getPriority());
    b.putInt("COMPLETE", task.get(position).getComplete());
    b.putLong("DUEDATE", task.get(position).getDueDateMillis());*/
    intent.putExtra("ID", String.valueOf(task.get(position).getId()));

    startActivity(intent);
    Log.v("testti", ""+v.getTag()+" apakah sama "+task.get(position).getId());
}

/* Click events on RecyclerView item checkboxes */
@Override
public void onItemToggled(boolean active, int position) {
    //TODO: Handle task item checkbox event
    ContentValues values = new ContentValues();

    String stringId = String.valueOf(task.get(position).getId());
    Log.v("test = ", ""+task.get(position).getId());

    Uri uri = DatabaseContract.CONTENT_URI;
    uri = uri.buildUpon().appendPath(stringId).build();

    if(active) {
        Log.v("test","tersentuh");
        //values.put(DatabaseContract.TaskColumns.IS_COMPLETE, 1);
        values.put(DatabaseContract.TaskColumns._ID, task.get(position).getId());
        values.put(DatabaseContract.TaskColumns.DESCRIPTION, task.get(position).getDescription());
        values.put(DatabaseContract.TaskColumns.IS_PRIORITY, task.get(position).getPriority());
        values.put(DatabaseContract.TaskColumns.IS_COMPLETE, 1);
        values.put(DatabaseContract.TaskColumns.DUE_DATE, task.get(position).getDueDateMillis());
        getContentResolver().update(uri,
                values,
                null,
                null);
    }else{
        Log.v("test","tidak tersentuh");
        values.put(DatabaseContract.TaskColumns._ID, task.get(position).getId());
        values.put(DatabaseContract.TaskColumns.DESCRIPTION, task.get(position).getDescription());
        values.put(DatabaseContract.TaskColumns.IS_PRIORITY, task.get(position).getPriority());
        values.put(DatabaseContract.TaskColumns.IS_COMPLETE, 0);
        values.put(DatabaseContract.TaskColumns.DUE_DATE, task.get(position).getDueDateMillis());
        getContentResolver().update(uri,
                values,
                null,
                null);
    }
    // re-queries for all tasks
    //mAdapter.notifyDataSetChanged();
    getSupportLoaderManager().restartLoader(TASKS_LOADER_ID, null, this);
}

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

    return new AsyncTaskLoader<Cursor>(this) {

        // Initialize a Cursor, this will hold all the task data
        Cursor mTaskData = null;

        // onStartLoading() is called when a loader first starts loading data
        @Override
        protected void onStartLoading() {
            if (mTaskData != null) {
                // Delivers any previously loaded data immediately
                deliverResult(mTaskData);
            } else {
                // Force a new load
                forceLoad();
            }
        }

        @Override
        public Cursor loadInBackground() {
            try {
                return getContentResolver().query(DatabaseContract.CONTENT_URI,
                        null,
                        null,
                        null,
                        DatabaseContract.DEFAULT_SORT);

            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }

        public void deliverResult(Cursor data) {
            mTaskData = data;
            super.deliverResult(data);
        }
    };

}

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

    while(data.moveToNext()){
        long id = data.getLong(data.getColumnIndex(DatabaseContract.TaskColumns._ID));
        String description = data.getString(data.getColumnIndex(DatabaseContract.TaskColumns.DESCRIPTION));
        long date = data.getLong(data.getColumnIndex(DatabaseContract.TaskColumns.DUE_DATE));
        int priority = data.getInt(data.getColumnIndex(DatabaseContract.TaskColumns.IS_PRIORITY));
        int complete = data.getInt(data.getColumnIndex(DatabaseContract.TaskColumns.IS_COMPLETE));

        try{
            Task tasks = new Task(data);
            task.add(tasks);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    mAdapter.swapCursor(data);
}

@Override
public void onLoaderReset(Loader<Cursor> loader) {
    mAdapter.swapCursor(null);
}

}

这是 recyclerview 适配器

public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.TaskHolder> {

/* Callback for list item click events */
public interface OnItemClickListener {
    void onItemClick(View v, int position);

    void onItemToggled(boolean active, int position);
}

/* ViewHolder for each task item */
public class TaskHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    public TaskTitleView nameView;
    public TextView dateView;
    public ImageView priorityView;
    public CheckBox checkBox;

    public TaskHolder(View itemView) {
        super(itemView);

        nameView = (TaskTitleView) itemView.findViewById(R.id.text_description);
        dateView = (TextView) itemView.findViewById(R.id.text_date);
        priorityView = (ImageView) itemView.findViewById(R.id.priority);
        checkBox = (CheckBox) itemView.findViewById(R.id.checkbox);

        itemView.setOnClickListener(this);
        checkBox.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v == checkBox) {
            completionToggled(this);
        } else {
            postItemClick(this);
        }
    }
}

private Cursor mCursor;
private OnItemClickListener mOnItemClickListener;
private Context mContext;

public TaskAdapter(Context mContext) {
    this.mContext = mContext;
}

public void setOnItemClickListener(OnItemClickListener listener) {
    mOnItemClickListener = listener;
}

private void completionToggled(TaskHolder holder) {
    if (mOnItemClickListener != null) {
        mOnItemClickListener.onItemToggled(holder.checkBox.isChecked(), holder.getAdapterPosition());
    }
}

private void postItemClick(TaskHolder holder) {
    if (mOnItemClickListener != null) {
        mOnItemClickListener.onItemClick(holder.itemView, holder.getAdapterPosition());
    }
}

@Override
public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    mContext = parent.getContext();
    View itemView = LayoutInflater.from(mContext)
            .inflate(R.layout.list_item_task, parent, false);

    return new TaskHolder(itemView);
}

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

    //TODO: Bind the task data to the views
    // Indices for the _id, description, and priority columns
    int idIndex = mCursor.getColumnIndex(DatabaseContract.TaskColumns._ID);
    int descriptionIndex = mCursor.getColumnIndex(DatabaseContract.TaskColumns.DESCRIPTION);
    int isCompleteIndex = mCursor.getColumnIndex(DatabaseContract.TaskColumns.IS_COMPLETE);
    int isPriorityIndex = mCursor.getColumnIndex(DatabaseContract.TaskColumns.IS_PRIORITY);
    int dueDateIndex = mCursor.getColumnIndex(DatabaseContract.TaskColumns.DUE_DATE);

    //move cursor to wanted data
    mCursor.moveToPosition(position);

    // Determine the values of the wanted data
    final int id = mCursor.getInt(idIndex);
    String description = mCursor.getString(descriptionIndex);
    int isComplete = mCursor.getInt(isCompleteIndex);
    int isPrior = mCursor.getInt(isPriorityIndex);
    long dueDate = mCursor.getLong(dueDateIndex);
    Log.v("adapter", " "+DateUtils.getRelativeTimeSpanString(mContext, dueDate)+" "+dueDate);
    CharSequence date = DateUtils.getRelativeTimeSpanString(mContext, dueDate);

    holder.itemView.setTag(id);

    //determine whether to show date or not
    if(dueDate != Long.MAX_VALUE){
        holder.dateView.setVisibility(View.VISIBLE);
        holder.dateView.setText(date);
    }else{
        holder.dateView.setVisibility(View.GONE);
    }
    //determine the priority icon
    if(isPrior == 0){
        holder.priorityView.setImageResource(R.drawable.ic_not_priority);
    }else{
        holder.priorityView.setImageResource(R.drawable.ic_priority);
    }
    //determine the text color and description
    holder.nameView.setText(description);
    holder.nameView.setState(isComplete);

    if(isComplete == 1){
        holder.checkBox.setChecked(true);
        holder.nameView.setState(isComplete);
    }

    //to chek if due date has passed or not
    Calendar now = Calendar.getInstance();
    Calendar tasksDate = Calendar.getInstance();
    tasksDate.setTimeInMillis(dueDate);
    int result = now.compareTo(tasksDate);

    if(result >= 0){
        holder.nameView.setState(2);
    }
    else if(result < 0){
        holder.nameView.setState(0);
    }


}

@Override
public int getItemCount() {
    return (mCursor != null) ? mCursor.getCount() : 0;
}

/**
 * Retrieve a {@link Task} for the data at the given position.
 *
 * @param position Adapter item position.
 *
 * @return A new {@link Task} filled with the position's attributes.
 */
public Task getItem(int position) {
    if (!mCursor.moveToPosition(position)) {
        throw new IllegalStateException("Invalid item position requested");
    }

    return new Task(mCursor);
}

@Override
public long getItemId(int position) {
    return getItem(position).id;
}

public Cursor swapCursor(Cursor cursor) {
    if (mCursor == cursor) {
        return null; // bc nothing has changed
    }
    Cursor temp = mCursor;
    this.mCursor = cursor; // new cursor value assigned

    //check if this is a valid cursor, then update the cursor
    if (cursor != null) {
        this.notifyDataSetChanged();
    }
    return temp;
}

}

如何解决这个问题?谢谢!

在您的代码的以下部分中,您没有提供 else 条件。

if (isComplete == 1){
    holder.checkBox.setChecked(true);
    holder.nameView.setState(isComplete);
}

您应该处理其他条件以取消选中复选框,因为 RecyclerView 使用旧视图。

holder.checkBox.setChecked(isComplete == 1);
holder.nameView.setState(isComplete);

此外,您应该在重新填充列表之前清除旧项目。