申请了一个应用程序,我将CursorTreeAdapter用作ExpandableListView.现在我想使用搜索框显示已过滤的ExpandableListView项目.喜欢这个:
这是我到目前为止的代码
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 开始).
现在唯一的办法是如何实现搜索?我应该通过内容提供者或数据库中的原始查询来做吗?我希望显示两个子表的结果.我认为,因为在打字时容易出错,这个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 .这变得更容易维护,以便您不必编写自己的数据库实现.
我现在尝试的是将我的约束保存在
FilterQueryProvider
的runQuery()中,然后在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的工作.我应该从MainActivity中运行哪些查询?
该项目可以下载 here ,您可以在Eclipse中导入.
我已经研究了你的问题,不幸的是我没有时间复制你的设置.但是,在通用术语中,您应该能够保存约束,然后在“getChildrenCursor”中运行针对该约束的查询:
Cursor getChildrenCursor(Cursor groupCursor) { if (mConstraint == null || mConstraint.isEmpty()) { // Normal query } else { // Constrained query } }
我不确定,但是当您在filterQueryProvider()中返回游标时,我很确定getChildrenCursor()将被调用以响应父游标的更改.然后,您只需管理约束的空/填充状态.
细节:
在你的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);
我不太确定的一件事是你要进行的自动扩展,我的过滤器可以工作,但是您需要重新打开并重新打开列表才能看到更改.
http://stackoverflow.com/questions/20585273/cursortreeadapter-with-search-implementation