15

のアプリケーションを作成しており、CursorTreeAdapterをExpandableListViewとして使用しています。フィルター処理された ExpandableListView アイテムを表示するために検索ボックスを使用したいと考えています。このような:

http://i.imgur.com/8ua7Mkl.png

これまでに書いたコードは次のとおりです。

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.javaKyle 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に保存してからFilterQueryProvidergetChildrenCursorその制約に対してクエリを実行することです。( 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バンドルから取得したクエリで実行する必要があるのは何ですか?

4

2 に答える 2

4

お客様の問題を調査しましたが、残念ながらお客様の設定を再現する時間がありません。ただし、一般的には、制約を保存してから、「getChildrenCursor」でその制約に対してクエリを実行できるはずです。

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

}

確かではありませんがgetChildrenCursor()filterQueryProvider(). 次に、制約の null/filled 状態を管理するだけです。

詳細:

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

よくわからないのは、あなたが行っている自動展開のことです。私のフィルターは機能しますが、変更を確認するには、リストを折りたたんで再度開く必要があります。

于 2014-01-13T21:38:33.530 に答える
2

あなたがすべきことは拡張FilterQueryProviderです。これにより、runQuery()フィルタリングされた結果の新しいカーソルを返す関数が提供されます (データベース クエリで実行される可能性があります)。

次に、CursorTreeAdapterアダプタの実装で、setFilterQueryProvider()メソッドを使用して FilterQueryProvider のインスタンスを提供します。

最後に、フィルタリングを実行する場合は、 を呼び出しますmAdapter.getFilter().filter("c")

ただし、実際にはSearchViewオートコンプリート機能を使用しておらず、代わりに独自のリストを作成しているため、選択したソリューションは必要以上に複雑です。代わりに、Content Provider と CursorTreeAdapter を削除して、より単純なリストまたはマップのメモリ内スキームを使用してアダプターをサポートしてみませんか? 必要に応じてメモリ内データを入力します (データセット全体がメモリに収まりますか?)。

于 2013-12-17T10:19:01.827 に答える