CursorTreeAdapter con implementación de búsqueda
Hago una solicitud paraandroide y estoy usando CursorTreeAdapter como ExpandableListView. Ahora quiero usar el cuadro de búsqueda para mostrar los elementos filtrados de ExpandableListView filtrados. Me gusta esto:
Aquí está el código que tengo 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 limpiado considerablemente el código (para que ustedes no tengan que hacerlo).
Como puede ver, tengo un total de 3 cursores (1 para los grupos y 2 para los niños). Los datos se obtienen deContactosContratar (Cuales 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 niño 2 representa todos los nombres de visualización de los contactos. (La mayoría de las funciones del cargador son deaquí).
Lo único es ahora, ¿cómo implemento una búsqueda? ¿Debo hacerlo a través del Proveedor de contenido o una consulta sin formato en la base de datos? Me gustaría que los resultados de ambas tablas de niños se muestren. Creo que porque es fácil cometer un error al escribir esotokenize=porter
Es una opción en mi caso.
Espero que alguien me pueda orientar en una buena dirección.
Editar:
He intentado esto enMyListAdapter.java
(conFilterQueryProvider
como lo sugiereKyle yo):
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 enMainActivity.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 solo devuelvo 1 consulta (nombres para mostrar) en lugar de 2 (nombres para mostrar y correos electrónicos) enrunQuery
?
Edición 2:
En primer lugar he cambiado todas las implementaciones de mi base de datos aContactosContratar. Esto se hace 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 enrunQuery()
deFilterQueryProvider
y luego engetChildrenCursor
ejecutar una consulta contra esa restricción. (como lo sugiereJRaymond)
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á elFilterQueryProvider
:
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 puede ver, he cargado la consulta de los grupos para obtener elgetChildrenCursor
trabajando. Sólo lo que para consulta debo ejecutar enMainActivity
que recibo del paquete?
El proyecto se puede descargar.aquí, que puedes importar en Eclipse.