CursorTreeAdapter con implementación de búsqueda

Hago una solicitud para android y estoy usando CursorTreeAdapter como ExpandableListView. Ahora quiero usar el cuadro de búsqueda para mostrar los elementos ExpandableListView filtrados. Me gusta esto: http://i.imgur.com/8ua7Mkl.png

Aquí está el código de lo que he llegado hasta ahora:

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 } } 

He simplificado y simplificado considerablemente el código (para que ustedes que no necesitan hacer).

Como puedes ver, tengo en total 3 cursores (1 para los grupos y 2 para los niños). Los datos se obtienen de ContactsContract (que son los contactos del usuario). El cursor del niño 1 representa todos los correos electrónicos de todos los contactos y el cursor del secundario 2 representa todos los nombres de visualización de los contactos. (La mayoría de las funciones del cargador es de aquí ).

Lo único es ahora ¿cómo implemento una búsqueda? ¿Debo hacerlo a través de Content Provider o una consulta en bruto en la base de datos? Me gustaría que los resultados de ambas tablas de niños se muestra. Creo que porque es fácil de hacer una falla al escribir que tokenize=porter es una opción en mi caso.

Espero que alguien me pueda apuntar en una buena dirección.

Editar:

He intentado esto en MyListAdapter.java (con FilterQueryProvider según lo sugerido por 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); } }; 

Y esto en 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; } }); 

Pero luego obtengo estos errores cuando intento buscar:

 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. 

¿Qué estoy haciendo mal? ¿O es esto porque sólo devuelvo una consulta (nombres de visualización) en lugar de 2 (nombres de pantalla y correos electrónicos) en runQuery ?

Editar 2:

En primer lugar, he cambiado todas las implementaciones de mi base de datos a ContactsContract . Esto es más fácil de mantener para que no tenga que escribir su propia implementación de base de datos.

Lo que ahora he intentado es guardar mi restricción en runQuery() de FilterQueryProvider , y luego en getChildrenCursor ejecutar una consulta contra esa restricción. (como sugiere 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; } 

Y aquí está 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); } }; 

Como se puede ver, he cargado la consulta de los grupos para conseguir que getChildrenCursor funcione. ¿Sólo qué para la pregunta debo funcionar en MainActivity que consigo del paquete?

El proyecto se puede descargar aquí , que puede importar en Eclipse.

He examinado su problema y, por desgracia, no tengo tiempo para replicar su configuración. En términos genéricos, sin embargo, debería ser capaz de guardar su restricción y, a continuación, en 'getChildrenCursor', ejecute una consulta contra esa restricción:

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

No estoy seguro, pero estoy bastante seguro de que getChildrenCursor() se llamará en respuesta a un cambio del cursor padre cuando devuelve el cursor en el filterQueryProvider() . A continuación, sólo gestionar el estado nulo / lleno de la restricción.

Detalles:

En su función filterList, en lugar de realizar un procedimiento complicado, simplemente llame a runQueryOnBackgroundThread(constraint); . Esto descargará automáticamente el trabajo de la base de datos al fondo. Guardar la restricción en su filterQueryProvider:

 String s = '%' + constraint.toString() + '%'; mConstraint = s; 

Para la consulta, sólo depende de lo que está tratando de salir de la base de datos – un ajuste rápido al código que publicó corre la consulta así:

 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); 

La única cosa que no estoy muy seguro es la cosa de expansión automática que tienes que ir, Mi filtro funciona, pero tienes que colapsar y abrir la lista de nuevo para ver el cambio.

Lo que debe hacer es extender FilterQueryProvider . Esto proporciona una función runQuery() que devuelve un nuevo cursor de resultados filtrados (probablemente realizado con una consulta de base de datos).

En su implementación del adaptador CursorTreeAdapter , a continuación, utilizará el método setFilterQueryProvider() para proporcionarle una instancia de su FilterQueryProvider.

Finalmente, cuando quiera realizar el filtrado llamará mAdapter.getFilter().filter("c") .

Sin embargo, al ver que en realidad no está utilizando las funciones de autocompletar de SearchView y, en lugar de ello, se utiliza su propia lista, la solución elegida es bastante más complicada de lo que necesita. ¿Por qué no dejas caer el Content Provider y CursorTreeAdapter y usas un esquema de listas o mapas más simple en memoria para respaldar tu adaptador? Rellene los datos en la memoria según sea necesario (¿puede encajar todo el conjunto de datos en la memoria?).

  • Bordes dentados en imágenes presentadas en Android
  • Discurso a texto en Android con coincidencia de palabras inusuales personalizada
  • No se muestran filas de tabla añadidas dinámicamente
  • Vaya a la página en epub reader (PageTurner)
  • ¿Cómo realizar una solicitud DELETE sin tipo de devolución o devolución de llamada?
  • Butterknife 8.4.0 no encuentra vistas después de volver a ejecutar la aplicación. Obtiene una excepción NullPointerException
  • Siempre vuelve a la pantalla de inicio de sesión
  • ¿Está la Multicast rota para Android 2.0.1 (actualmente en el DROID) o me falta algo?
  • Disposición de Android con ListView y botones
  • Android fill singlechoiceitems diálogo con los valores arraylist
  • Setter y Getter funciones. En Android. Rendimiento de gastos generales?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.