如何从 CursorLoader 中删除除选中项以外的所有项?

How to remove all but the checked item from a CursorLoader?

我有一个 activity 可以为用户 select 显示医生的列表视图。每个列表视图项都包含一个复选框和一个医生的名字。我的最终目标是当用户检查医生时,所有其他人都将被删除,只剩下 selected 医生。

在适配器类中,我有以下代码片段:

viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if(isChecked) {
            swapCursor(cursor);
        }
    }
});

我有存储前一个光标的代码,因此如果医生未被选中,它可以恢复,但我删除了它以简化这个问题。目前,当我选中复选框时没有任何反应。我尝试添加 notifyDataSetChanged() 但适配器没有注册观察者,因此没有任何区别。我怎样才能让 activity 知道某项已被 select 编辑?

documentation 我知道我应该注册一个 DataSetObserver 但我不知道应该注册哪个对象以及如何注册。

编辑

这是我的适配器类的更多内容:

public MyAdapter(Context context, Cursor cursor, int flags){
    super(context, cursor, flags);
}

@Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
    View view = LayoutInflater.from(context).inflate(R.layout.adapter_layout, viewGroup, false);
    ViewHolder viewHolder = new ViewHolder(view);
    view.setTag(viewHolder);
    return view;
}

@Override
public void bindView(View view, final Context context, final Cursor cursor) {
    ViewHolder viewHolder = (ViewHolder) view.getTag();

    String firstName = cursor.getString(cursor.getColumnIndex(DoctorEntry.COLUMN_FIRSTNAME));
    String lastName = cursor.getString(cursor.getColumnIndex(DoctorEntry.COLUMN_LASTNAME));
    String suffix = cursor.getString(cursor.getColumnIndex(DoctorEntry.COLUMN_SUFFIX));

    viewHolder.nameView.setText(firstName + " " + lastName + ", " + suffix);

    // Should I add something here?
    viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

        }
    });
}

public static class ViewHolder{
    public final CheckBox checkBox;
    public final TextView nameView;

    public ViewHolder(View view){
        checkBox = (CheckBox) view.findViewById(R.id.chooseDoctorCheckBox);
        nameView = (TextView) view.findViewById(R.id.chooseDoctorLabel);
    }
}

适配器用于 activity,它实现了 CursorLoader 以将数据加载到列表视图中。在 activity 中,我可以打电话给 doctorAdapter.swapCursor(someCursorIWant); 但我不能参考 selected 医生。我试过的东西:

// Set currentDoctor (a property of the adapter class) if a new one is checked.
// Again, other checks removed for simplicity.
viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if(isChecked){
            currentDoctor = cursor;
            notifyDataSetChanged();
        }
    }
});

然后在activity:

// Set observers
mDoctorAdapter.registerDataSetObserver(new DataSetObserver() {
    @Override
    public void onChanged() {
        Cursor c = mDoctorAdapter.getCheckedDoctor();
        if(c != null)
           mDoctorAdapter.swapCursor(c);
    }
});

但它并没有按照我的意愿进行。

您需要做的是从光标中删除选中的项目,但您不能直接这样做,因此您需要 ContentResolver#delete() 调用或 SQL

编辑:

不能只从光标处删除。需要重新查询过滤后的结果,这样你改变checked状态后,也改变一个flag

您可以通过两种方式实现:

1) 使用 FilterQueryProvider 您设置一个 filterQueryProvider 和一个查询值,如果它运行它将 select 指定的医生。当检查医生时,您将 filterQueryProvider 设置为带有 constraint value 的适配器,例如等于 doctor id。在 filterQueryProviderrunQuery 中,你必须 return 一个游标,select 一个有 constraint id 的医生。然后每次检查医生时,您只需致电 adapter.getFilter().filter(doctoreId)

您还应该重写 changeCursor,这样它就不会关闭前一个游标 (加载程序 returns 的游标) 因为关闭了游标是装载机的责任。 因此,在您的情况下,请检查光标大小,如果它等于 1,则关闭它,但如果不是,则不要关闭它。

2) 向您的内容提供者添加一个新的 uri,该内容提供者用于 return 只是一个医生的 ID。然后,当用户检查新医生时,您会调用一个新的 initLoader,其中包含加载器 ID 和 select 医生所需的信息包。

我已经解决了这个问题。我所做的是调整 bindView() 方法以在选择时存储医生的 ID:

public void bindView(View view, Context context, Cursor cursor) {
    ViewHolder viewHolder = (ViewHolder) view.getTag();

    final long id = cursor.getLong(cursor.getColumnIndex(DoctorEntry._ID));
    String firstName = cursor.getString(cursor.getColumnIndex(DoctorEntry.COLUMN_FIRSTNAME));
    String lastName = cursor.getString(cursor.getColumnIndex(DoctorEntry.COLUMN_LASTNAME));
    String suffix = cursor.getString(cursor.getColumnIndex(DoctorEntry.COLUMN_SUFFIX));

    viewHolder.nameView.setText(firstName + " " + lastName + ", " + suffix);

    viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if(isChecked){
                selectedDoctorId = id;
            } else{
                selectedDoctorId = 0;
            }
            notifyDataSetChanged();
        }
    });
}

然后,我在 activity 中注册了一个新的观察者,在 onChanged() 方法中,我提取了 id 并使用新的 Uri 来查询那个医生:

// Add observer for doctor
mDoctorAdapter.registerDataSetObserver(new DataSetObserver() {
    @Override
    public void onChanged() {
        if(!systemEntered && mDoctorAdapter.getSelectedDoctorId() != 0){
            Cursor c = getContentResolver().query(
                    DoctorEntry.buildDoctorUri(mDoctorAdapter.getSelectedDoctorId()),
                    DOCTOR_COLUMNS,
                    null,
                    null,
                    null
            );
            systemEntered = true;
            mDoctorAdapter.swapCursor(c);
            systemEntered = false;
        }
    }
});

我不得不使用一个名为 systemEntered 的布尔标志来防止无限循环。没有它,调用 swapCursor() 会再次触发 onChanged(),然后我重新查询,依此类推。现在,当我选中特定医生的复选框时,所有其他医生都会从列表中删除。感谢所有花时间研究这个的人。


编辑

根据评论中的建议,我将其更改为使用回调。在我的适配器 class 中,我包含了以下接口和方法:

private DoctorAdapterCallbacks mCallbacks;

viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if(isChecked && mCallbacks != null){
            mCallbacks.onDoctorChecked(id);
        }
    }
});

public void onRegisterCallbacks(Activity activity){
    mCallbacks = (DoctorAdapterCallbacks) activity;
}

public static interface DoctorAdapterCallbacks{
    void onDoctorChecked(long id);
}

并且在我的 activity(实现接口)中添加了以下代码:

// Set callbacks (This is in the onCreate method)
mDoctorAdapter.onRegisterCallbacks(this);

@Override
public void onDoctorChecked(long id) {
    Bundle args = new Bundle();
    args.putLong(SELECTED_DOCTOR_ID, id);
    getSupportLoaderManager().initLoader(SELECTED_DOCTOR_LOADER, args, this);
}

我创建了另一个 CursorLoader,旨在仅处理一个医生,并且该加载器将替换前一个(所有医生中的)。再次感谢所有的帮助和建议。