Android用のアプリケーションを作成しており、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
(Kyle I.FilterQueryProvider
の提案による):
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.
私が間違っていることは何ですか?それとも、2 つのクエリ (表示名と電子メール) ではなく 1 つのクエリ (表示名) しか返さないためですrunQuery
か?
編集2:
まず、すべてのデータベース実装をContactsContractに変更しました。これにより、独自のデータベース実装を作成する必要がなくなるため、保守が容易になります。
私が今試したことは、自分の制約をrunQuery()
ofに保存してからFilterQueryProvider
、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
バンドルから取得したクエリで実行する必要があるのは何ですか?