CursorTreeAdapter avec la mise en œuvre de la recherche

Je fais une demande pour Android et j'utilise CursorTreeAdapter comme ExpandableListView. Maintenant, je souhaite utiliser le champ de recherche pour afficher les éléments ExpandableListView filtrés. Comme ça: Http://i.imgur.com/8ua7Mkl.png

Voici le code de ce que j'ai jusqu'ici:

  • Les champs statiques dans les classes d'activité sont-ils garantis pour survivre à un cycle de création / destruction?
  • Synchronisation des contacts avec ma propre application comme skype ou whatsapp
  • Comment se débarrasser de l'avertissement: utilisez '$' au lieu de '.' Pour les classes intérieures dans Eclipse
  • GetString en dehors d'un contexte ou d'une activité
  • Devriez-vous utiliser AccountManager pour stocker les noms d'utilisateur et les mots de passe pour une application Android?
  • Obtenir l'en-tête de HttpResponse dans Android
  • 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 } } 

    J'ai considérablement simplifié et nettoyé le code (de sorte que les gars qui ne doivent pas faire).

    Comme vous pouvez le constater, j'ai totalisé 3 curseurs (1 pour les groupes et 2 pour les enfants). Les données proviennent de ContactsContract (qui sont les contacts de l'utilisateur). Le curseur de l'enfant 1 représente tous les e-mails de tous les contacts et le curseur de l'enfant 2 représente tous les noms d'affichage des contacts. (La plupart des fonctions du chargeur sont à partir d' ici ).

    La seule chose est maintenant comment puis-je implémenter une recherche? Devrais-je le faire à travers un fournisseur de contenu ou une requête brute dans la base de données? J'aimerais que les résultats des deux tables enfants s'affichent. Je pense que c'est facile de faire une faute en tapant que tokenize=porter est une option dans mon cas.

    J'espère que quelqu'un peut me pointer dans une bonne direction.

    Modifier:

    J'ai essayé ceci dans MyListAdapter.java (avec FilterQueryProvider comme suggéré par 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); } }; 

    Et ceci dans 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; } }); 

    Mais alors, j'ai ces erreurs lorsque j'essaie de rechercher:

     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'est-ce que je fais mal? Ou est-ce parce que je ne suis que la requête du retour 1 (noms d'affichage) au lieu de 2 (noms d'écran et e-mails) dans runQuery ?

    Modifier 2:

    Tout d'abord, j'ai changé toutes les implémentations de ma base de données dans ContactsContract . Cela devient plus facile à entretenir afin que vous ne devez pas écrire votre propre implémentation de base de données.

    Ce que j'ai maintenant essayé est de sauvegarder ma contrainte dans runQuery() de FilterQueryProvider , puis dans getChildrenCursor exécuter une requête contre cette contrainte. (Comme l'a suggéré 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; } 

    Et voici 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); } }; 

    Comme vous pouvez le voir, je charge la requête des groupes afin d'obtenir le getChildrenCursor du getChildrenCursor . MainActivity ce que ce que pour la requête je devrais exécuter dans MainActivity que je reçois du paquet?

    Le projet peut être téléchargé ici , que vous pouvez importer dans Eclipse.

  • Base de données Android - Impossible d'effectuer cette opération car le pool de connexion a été fermé
  • 2 Solutions collect form web for “CursorTreeAdapter avec la mise en œuvre de la recherche”

    J'ai examiné votre problème et, malheureusement, je n'ai pas le temps de reproduire votre configuration. En termes génériques, cependant, vous devriez pouvoir sauvegarder votre contrainte, puis dans 'getChildrenCursor', exécutez une requête contre cette contrainte:

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

    Je ne suis pas certain, mais je suis certain que getChildrenCursor() sera appelé en réponse à une modification du curseur parent lorsque vous renvoyez le curseur dans filterQueryProvider() . Vous venez de gérer l'état nul ou rempli de la contrainte.

    Détails:

    Dans votre fonction filterList, au lieu de faire une procédure compliquée, il suffit d'appeler runQueryOnBackgroundThread(constraint); . Cela déchargera automatiquement le travail de la base de données en arrière-plan. Enregistrez votre contrainte dans votre filtreQueryProvider:

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

    Pour la requête, cela dépend simplement de ce que vous essayez de sortir de la base de données: un ajustement rapide du code que vous avez posté exécute la requête comme suit:

     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 seule chose dont je ne suis pas trop sûr est la chose que vous développez automatiquement, mon filtre fonctionne mais vous devez vous effondrer et ouvrir la liste pour voir la modification.

    Ce que vous devez faire, c'est étendre FilterQueryProvider . Cela fournit une fonction runQuery() qui renvoie un nouveau curseur des résultats filtrés (probablement accompli avec une requête de base de données).

    Dans votre CursorTreeAdapter adaptateur CursorTreeAdapter vous utiliserez ensuite la méthode setFilterQueryProvider() pour lui fournir une instance de votre FilterQueryProvider.

    Enfin, lorsque vous souhaitez effectuer un filtrage, vous allez appeler mAdapter.getFilter().filter("c") .

    Cependant, comme vous n'utilisez pas réellement les fonctionnalités de SearchView automatique de SearchView et SearchView plutôt votre propre liste, votre solution choisie est un peu plus compliquée qu'elle ne l'est. Pourquoi ne déposez-vous pas plutôt The Content Provider et CursorTreeAdapter et utilisez-il un schéma de listes ou de cartes plus simples pour sauvegarder votre carte? Remplissez les données dans la mémoire selon vos besoins (votre ensemble de données peut-il être adapté à la mémoire?).

    coAndroid est un fan Android de Google, tout sur les téléphones Android, Android Wear, Android Dev et Android Games Apps.