java – 具有搜尋實現的CursorTreeAdapter
申請了一個應用程式,我將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中執行哪些查詢?
我已經研究了你的問題,不幸的是我沒有時間複製你的設定.但是,在通用術語中,您應該能夠儲存約束,然後在“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