我正在为android制作一个应用程序,并且正在使用CursorTreeAdapter作为ExpandableListView。我想使用搜索框显示过滤后的ExpandableListView项目。像这样:
java - 具有搜索实现的CursorTreeAdapter-LMLPHP
这是我到目前为止编写的代码:
MainActivity.java :

package com.example.cursortreeadaptersearch;

import java.util.HashMap;

import android.app.SearchManager;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.ContactsContract;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import android.widget.ExpandableListView;
import android.widget.SearchView;
import android.widget.SearchView.OnCloseListener;
import android.widget.SearchView.OnQueryTextListener;

import com.actionbarsherlock.app.SherlockFragmentActivity;

public class MainActivity extends SherlockFragmentActivity {

    private SearchView search;
    private MyListAdapter listAdapter;
    private ExpandableListView myList;

    private final String DEBUG_TAG = getClass().getSimpleName().toString();

    /**
     * The columns we are interested in from the database
     */
    static final String[] CONTACTS_PROJECTION = new String[] {
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME,
            ContactsContract.Contacts.PHOTO_ID,
            ContactsContract.CommonDataKinds.Email.DATA,
            ContactsContract.CommonDataKinds.Photo.CONTACT_ID };

    static final String[] GROUPS_SUMMARY_PROJECTION = new String[] {
            ContactsContract.Groups.TITLE, ContactsContract.Groups._ID,
            ContactsContract.Groups.SUMMARY_COUNT,
            ContactsContract.Groups.ACCOUNT_NAME,
            ContactsContract.Groups.ACCOUNT_TYPE,
            ContactsContract.Groups.DATA_SET };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        search = (SearchView) findViewById(R.id.search);
        search.setSearchableInfo(searchManager
                .getSearchableInfo(getComponentName()));
        search.setIconifiedByDefault(false);
        search.setOnQueryTextListener(new OnQueryTextListener() {

            @Override
            public boolean onQueryTextSubmit(String query) {
                listAdapter.filterList(query);
                expandAll();
                return false;
            }

            @Override
            public boolean onQueryTextChange(String query) {
                listAdapter.filterList(query);
                expandAll();
                return false;
            }
        });

        search.setOnCloseListener(new OnCloseListener() {

            @Override
            public boolean onClose() {
                listAdapter.filterList("");
                expandAll();
                return false;
            }
        });

        // get reference to the ExpandableListView
        myList = (ExpandableListView) findViewById(R.id.expandableList);
        // create the adapter
        listAdapter = new MyListAdapter(null, MainActivity.this);
        // attach the adapter to the list
        myList.setAdapter(listAdapter);

        Loader<Cursor> loader = getSupportLoaderManager().getLoader(-1);
        if (loader != null && !loader.isReset()) {
            runOnUiThread(new Runnable() {
                public void run() {
                    getSupportLoaderManager().restartLoader(-1, null,
                            mSpeakersLoaderCallback);
                }
            });
        } else {
            runOnUiThread(new Runnable() {
                public void run() {
                    getSupportLoaderManager().initLoader(-1, null,
                            mSpeakersLoaderCallback).forceLoad();
                    ;
                }
            });
        }

    }

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

        getApplicationContext().getContentResolver().registerContentObserver(
                ContactsContract.Data.CONTENT_URI, true,
                mSpeakerChangesObserver);
    }

    @Override
    public void onPause() {
        super.onPause();

        getApplicationContext().getContentResolver().unregisterContentObserver(
                mSpeakerChangesObserver);
    }

    // method to expand all groups
    private void expandAll() {
        int count = listAdapter.getGroupCount();
        for (int i = 0; i < count; i++) {
            myList.expandGroup(i);
        }
    }

    public LoaderManager.LoaderCallbacks<Cursor> mSpeakersLoaderCallback = new LoaderCallbacks<Cursor>() {

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            Log.d(DEBUG_TAG, "onCreateLoader for loader_id " + id);
            CursorLoader cl = null;

            HashMap<Integer, Integer> groupMap = listAdapter.getGroupMap();
            if (id != -1) {
                int groupPos = groupMap.get(id);
                if (groupPos == 0) { // E-mail group
                    String[] PROJECTION = new String[] {
                            ContactsContract.RawContacts._ID,
                            ContactsContract.CommonDataKinds.Email.DATA };
                    String sortOrder = "CASE WHEN "
                            + ContactsContract.Contacts.DISPLAY_NAME
                            + " NOT LIKE '%@%' THEN 1 ELSE 2 END, "
                            + ContactsContract.Contacts.DISPLAY_NAME + ", "
                            + ContactsContract.CommonDataKinds.Email.DATA
                            + " COLLATE NOCASE";
                    String selection = ContactsContract.CommonDataKinds.Email.DATA
                            + " NOT LIKE ''";
                    cl = new CursorLoader(getApplicationContext(),
                            ContactsContract.CommonDataKinds.Email.CONTENT_URI,
                            PROJECTION, selection, null, sortOrder);
                } else if (groupPos == 1) { // Name group
                    Uri contactsUri = ContactsContract.Data.CONTENT_URI;
                    String selection = "(("
                            + ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME
                            + " NOTNULL) AND ("
                            + ContactsContract.CommonDataKinds.GroupMembership.HAS_PHONE_NUMBER
                            + "=1) AND ("
                            + ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME
                            + " != '') AND ("
                            + ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID
                            + " = '1' ))"; // Row ID 1 == All contacts
                    String sortOrder = ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME
                            + " COLLATE LOCALIZED ASC";

                    cl = new CursorLoader(getApplicationContext(), contactsUri,
                            CONTACTS_PROJECTION, selection, null, sortOrder);
                }
            } else {
                // group cursor
                Uri groupsUri = ContactsContract.Groups.CONTENT_SUMMARY_URI;
                String selection = "((" + ContactsContract.Groups.TITLE
                        + " NOTNULL) AND (" + ContactsContract.Groups.TITLE
                        + " == 'Coworkers' ) OR ("
                        + ContactsContract.Groups.TITLE
                        + " == 'My Contacts' ))"; // Select only Coworkers
                                                 // (E-mail only) and My
                                                // Contacts (Name only)
                String sortOrder = ContactsContract.Groups.TITLE
                        + " COLLATE LOCALIZED ASC";
                cl = new CursorLoader(getApplicationContext(), groupsUri,
                        GROUPS_SUMMARY_PROJECTION, selection, null, sortOrder);
            }

            return cl;
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            // Swap the new cursor in.
            int id = loader.getId();
//          Log.d("Dump Cursor MainActivity",
//                  DatabaseUtils.dumpCursorToString(data));
            Log.d(DEBUG_TAG, "onLoadFinished() for loader_id " + id);
            if (id != -1) {
                // child cursor
                if (!data.isClosed()) {
                    Log.d(DEBUG_TAG, "data.getCount() " + data.getCount());

                    HashMap<Integer, Integer> groupMap = listAdapter
                            .getGroupMap();
                    try {
                        int groupPos = groupMap.get(id);
                        Log.d(DEBUG_TAG, "onLoadFinished() for groupPos "
                                + groupPos);
                        listAdapter.setChildrenCursor(groupPos, data);
                    } catch (NullPointerException e) {
                        Log.w("DEBUG",
                                "Adapter expired, try again on the next query: "
                                        + e.getMessage());
                    }
                }
            } else {
                listAdapter.setGroupCursor(data);
            }
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            // This is called when the last Cursor provided to onLoadFinished()
            // is about to be closed.
            int id = loader.getId();
            Log.d(DEBUG_TAG, "onLoaderReset() for loader_id " + id);
            if (id != 1) {
                // child cursor
                try {
                    listAdapter.setChildrenCursor(id, null);
                } catch (NullPointerException e) {
                    Log.w(DEBUG_TAG,
                            "Adapter expired, try again on the next query: "
                                    + e.getMessage());
                }
            } else {
                listAdapter.setGroupCursor(null);
            }
        }
    };

    private ContentObserver mSpeakerChangesObserver = new ContentObserver(
            new Handler()) {

        @Override
        public void onChange(boolean selfChange) {
            if (getApplicationContext() != null) {
                runOnUiThread(new Runnable() {
                    public void run() {
                        getSupportLoaderManager().restartLoader(-1, null,
                                mSpeakersLoaderCallback);
                    }
                });
            }
        }
    };
}
MyListAdapter.java :
package com.example.cursortreeadaptersearch;

import java.util.HashMap;

import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorTreeAdapter;
import android.widget.TextView;

public class MyListAdapter extends CursorTreeAdapter {

    public HashMap<String, View> childView = new HashMap<String, View>();

    /**
     * The columns we are interested in from the database
     */

    private final String DEBUG_TAG = getClass().getSimpleName().toString();

    protected final HashMap<Integer, Integer> mGroupMap;

    private MainActivity mActivity;
    private LayoutInflater mInflater;

    String mConstraint;

    public MyListAdapter(Cursor cursor, Context context) {

        super(cursor, context);
        mActivity = (MainActivity) context;
        mInflater = LayoutInflater.from(context);
        mGroupMap = new HashMap<Integer, Integer>();
    }

    @Override
    public View newGroupView(Context context, Cursor cursor,
            boolean isExpanded, ViewGroup parent) {

        final View view = mInflater.inflate(R.layout.list_group, parent, false);
        return view;
    }

    @Override
    public void bindGroupView(View view, Context context, Cursor cursor,
            boolean isExpanded) {

        TextView lblListHeader = (TextView) view
                .findViewById(R.id.lblListHeader);

        if (lblListHeader != null) {
            lblListHeader.setText(cursor.getString(cursor
                    .getColumnIndex(ContactsContract.Groups.TITLE)));
        }
    }

    @Override
    public View newChildView(Context context, Cursor cursor,
            boolean isLastChild, ViewGroup parent) {

        final View view = mInflater.inflate(R.layout.list_item, parent, false);

        return view;
    }

    @Override
    public void bindChildView(View view, Context context, Cursor cursor,
            boolean isLastChild) {

        TextView txtListChild = (TextView) view.findViewById(R.id.lblListItem);

        if (txtListChild != null) {
            txtListChild.setText(cursor.getString(1)); // Selects E-mail or
                                                        // Display Name
        }

    }

    protected Cursor getChildrenCursor(Cursor groupCursor) {
        // Given the group, we return a cursor for all the children within that
        // group
        int groupPos = groupCursor.getPosition();
        int groupId = groupCursor.getInt(groupCursor
                .getColumnIndex(ContactsContract.Groups._ID));

        Log.d(DEBUG_TAG, "getChildrenCursor() for groupPos " + groupPos);
        Log.d(DEBUG_TAG, "getChildrenCursor() for groupId " + groupId);

        mGroupMap.put(groupId, groupPos);

        Loader loader = mActivity.getSupportLoaderManager().getLoader(groupId);
        if (loader != null && !loader.isReset()) {
            mActivity.getSupportLoaderManager().restartLoader(groupId, null,
                    mActivity.mSpeakersLoaderCallback);
        } else {
            mActivity.getSupportLoaderManager().initLoader(groupId, null,
                    mActivity.mSpeakersLoaderCallback);
        }

        return null;
    }

    // Access method
    public HashMap<Integer, Integer> getGroupMap() {
        return mGroupMap;
    }

    public void filterList(CharSequence constraint) {
        // TODO Filter the data here
    }
}
我已经非常简化和清理了代码(以便您不需要这样做)。
如您所见,我总共有3个游标(组1个,子级2个)。数据来自ContactsContract(用户的联系人)。
子级1的光标代表所有联系人的所有电子邮件,子级2的光标代表联系人的所有显示名称。 (大多数加载程序功能来自here)。
现在唯一的是如何实现搜索?我应该通过Content Provider还是通过数据库中的原始查询进行处理?我希望显示两个子表的结果。我认为是因为在输入tokenize=porter是我的情况下很容易出错。
我希望有人可以指出我的正确方向。
编辑:
我已经在 MyListAdapter.java 中尝试过此操作(由 FilterQueryProvider 建议使用Kyle I.):
public void filterList(CharSequence constraint) {
    final Cursor oldCursor = getCursor();
    setFilterQueryProvider(filterQueryProvider);
    getFilter().filter(constraint, new FilterListener() {
        public void onFilterComplete(int count) {
            // assuming your activity manages the Cursor
            // (which is a recommended way)
            notifyDataSetChanged();
//          stopManagingCursor(oldCursor);
//          final Cursor newCursor = getCursor();
//          startManagingCursor(newCursor);
//          // safely close the oldCursor
            if (oldCursor != null && !oldCursor.isClosed()) {
                oldCursor.close();
            }
        }
    });
}

private FilterQueryProvider filterQueryProvider = new FilterQueryProvider() {
    public Cursor runQuery(CharSequence constraint) {
        // assuming you have your custom DBHelper instance
        // ready to execute the DB request
        String s = '%' + constraint.toString() + '%';
        return mActivity.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
                MainActivity.CONTACTS_PROJECTION,
                ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME + " LIKE ?",
            new String[] { s },
            null);
    }
};
这在 MainActivity.java 中:
        search.setOnQueryTextListener(new OnQueryTextListener() {

            @Override
            public boolean onQueryTextSubmit(String query) {
                listAdapter.filterList(query);
                expandAll();
                return false;
            }

            @Override
            public boolean onQueryTextChange(String query) {
                listAdapter.filterList(query);
                expandAll();
                return false;
            }
        });

        search.setOnCloseListener(new OnCloseListener() {

            @Override
            public boolean onClose() {
                listAdapter.filterList("");
                expandAll();
                return false;
            }
        });
但是当我尝试搜索时出现以下错误:
12-20 13:20:19.449: E/CursorWindow(28747): Failed to read row 0, column -1 from a CursorWindow which has 96 rows, 4 columns.
12-20 13:20:19.449: D/AndroidRuntime(28747): Shutting down VM
12-20 13:20:19.449: W/dalvikvm(28747): threadid=1: thread exiting with uncaught exception (group=0x415c62a0)
12-20 13:20:19.499: E/AndroidRuntime(28747): FATAL EXCEPTION: main
12-20 13:20:19.499: E/AndroidRuntime(28747): java.lang.IllegalStateException: Couldn't read row 0, col -1 from CursorWindow.  Make sure the Cursor is initialized correctly before accessing data from it.
我做错了什么?还是这是因为我在runQuery中只返回了1个查询(显示名称)而不是2个(显示名称和电子邮件)?
编辑2:
首先,我已将所有数据库实现更改为ContactsContract。这变得易于维护,因此您不必编写自己的数据库实现。
我现在尝试的是将约束保存在 runQuery() FilterQueryProvider中,然后在getChildrenCursor中针对该约束运行查询。 (如JRaymond所建议)
private String mConstraint;
protected Cursor getChildrenCursor(Cursor groupCursor) {
    // Given the group, we return a cursor for all the children within that
    // group
    int groupPos = groupCursor.getPosition();
    int groupId = groupCursor.getInt(groupCursor
            .getColumnIndex(ContactsContract.Groups._ID));

    Log.d(DEBUG_TAG, "getChildrenCursor() for groupPos " + groupPos);
    Log.d(DEBUG_TAG, "getChildrenCursor() for groupId " + groupId);

    mGroupMap.put(groupId, groupPos);

    Bundle b = new Bundle();
    b.putString("constraint", mConstraint);

    Loader loader = mActivity.getSupportLoaderManager().getLoader(groupId);
    if (loader != null && !loader.isReset()) {
        if (mConstraint == null || mConstraint.isEmpty()) {
            // Normal query
            mActivity.getSupportLoaderManager().restartLoader(groupId,
                    null, mActivity.mSpeakersLoaderCallback);
        } else {
            // Constrained query
            mActivity.getSupportLoaderManager().restartLoader(groupId, b,
                    mActivity.mSpeakersLoaderCallback);

        }
    } else {
        if (mConstraint == null || mConstraint.isEmpty()) {
            // Normal query
            mActivity.getSupportLoaderManager().initLoader(groupId, null,
                    mActivity.mSpeakersLoaderCallback);
        } else {
            // Constrained query
            mActivity.getSupportLoaderManager().initLoader(groupId, b,
                    mActivity.mSpeakersLoaderCallback);
        }
    }

    return null;
}
这是 FilterQueryProvider :
private FilterQueryProvider filterQueryProvider = new FilterQueryProvider() {
    public Cursor runQuery(CharSequence constraint) {
        // Load the group cursor here and assign mConstraint
        mConstraint = constraint.toString();
        Uri groupsUri = ContactsContract.Groups.CONTENT_SUMMARY_URI;
        String selection = "((" + ContactsContract.Groups.TITLE
                + " NOTNULL) AND (" + ContactsContract.Groups.TITLE
                + " == 'Coworkers' ) OR (" + ContactsContract.Groups.TITLE
                + " == 'My Contacts' ))"; // Select only Coworkers
                                            // (E-mail only) and My
                                            // Contacts (Name only)
        String sortOrder = ContactsContract.Groups.TITLE
                + " COLLATE LOCALIZED ASC";
        return mActivity.getContentResolver().query(groupsUri,
                MainActivity.GROUPS_SUMMARY_PROJECTION, selection, null,
                sortOrder);
    }
};
如您所见,为了使getChildrenCursor运行正常,我已经加载了组的查询。我应该从 bundle 包中获取的MainActivity中仅运行什么查询?

最佳答案

我们已经调查了您的问题,很遗憾,我没有时间来复制您的设置。概括地说,您应该能够保存约束,然后在“getChildrenCursor”中针对该约束运行查询:

Cursor getChildrenCursor(Cursor groupCursor) {
  if (mConstraint == null || mConstraint.isEmpty()) {
    // Normal query
  } else {
    // Constrained query
  }

}

我不确定,但是我很确定当您在getChildrenCursor()中返回游标时,会响应父游标的更改而调用filterQueryProvider()。然后,您只需管理约束的空/填充状态。

详细信息:

在您的filterList函数中,无需执行复杂的过程,只需调用runQueryOnBackgroundThread(constraint);即可。这将自动将数据库工作卸载到后台。将约束保存在filterQueryProvider中:
String s = '%' + constraint.toString() + '%';
mConstraint = s;

对于查询,它仅取决于您要从数据库中获取的内容-对发布的代码进行快速调整即可运行查询,如下所示:
String selection = ContactsContract.CommonDataKinds.Email.DATA
    + " NOT LIKE ''";
if (constraint != null) {
    selection += " AND " + ContactsContract.CommonDataKinds.Email.DATA + " LIKE ?";
}
cl = new CursorLoader(getApplicationContext(),
    ContactsContract.CommonDataKinds.Email.CONTENT_URI,
    PROJECTION, selection, constraint, sortOrder);

我不太确定的一件事是您要进行自动扩展,我的过滤器可以运行,但是您需要折叠并再次打开列表才能看到更改。

08-27 15:27