web-dev-qa-db-ja.com

CursorLoaderに裏打ちされたAutoCompleteTextView

そのため、カスタムMultiAutoCompleteTextViewを同時に使用しながら、CursorLoaderを拡張してTokenizerでバックアップするのに問題があります。この問題は、特にmAdapter.setCursorToStringConverter();呼び出しで発生します。引数としてカーソルを持つconvertToString()メソッドには、このメソッドの最初の呼び出し時に有効で閉じられていないカーソルがあります。ただし、後続の呼び出しでは、カーソルがnullまたは閉じられます。これは、LoaderManagerCursorLoaderを管理する方法と関係があると思います。

setCursorToStringConverter()メソッドをコメントアウトすると、このビューに入力したテキストに基づいて使用可能な選択肢のリストが表示されます。ただし、convertToString()メソッドが実装されていないため、カスタムTokenizerterminateToken()メソッドは、意図した文字列ではなく、カーソルオブジェクトの代表的な文字列を受け取ります。結果のクエリで目的の列の現在の文字列値を取得するためにカーソルが使用されていません。

3つのクラス(_CursorLoader/LoaderManger_、MultiAutoCompleteTextView、およびTokenizer)の組み合わせを実装できる人はいますか?

私はこれで正しい方向に進んでいますか、それともこれは単に不可能ですか?

カスタムMultiAutoCompleteTextViewとともにSimpleCursorAdapterに裏打ちされたカスタムTokenizerを実装することができました。厳密モードではCursorLoaderのカーソルが明示的に閉じられていないと文句を言うので、代わりにMultiAutoCompleteTextViewを使用してこれを実装できるかどうか疑問に思っていました。

どんな助けでも大歓迎です。

_public class CustomMultiAutoCompleteTextView extends MultiAutoCompleteTextView
  implements LoaderManager.LoaderCallbacks<Cursor> {

    private final String DEBUG_TAG = getClass().getSimpleName().toString();
    private Messenger2 mContext;
    private RecipientsCursorAdapter mAdapter;
    private ContentResolver mContentResolver;
    private final char delimiter = ' ';
    private CustomMultiAutoCompleteTextView mView;

    // If non-null, this is the current filter the user has provided.
    private String mCurFilter;

    // These are the Contacts rows that we will retrieve.
    final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
        ContactsContract.Contacts._ID,
        ContactsContract.Contacts.DISPLAY_NAME };

    public CustomMultiAutoCompleteTextView(Context c) {
        super(c);
        init(c);
    }

    public CustomMultiAutoCompleteTextView(Context c, AttributeSet attrs) {
        super(c, attrs);
        init(c);
    }

    private void init(Context context) {
        mContext = (Messenger2) context;
        mContentResolver = mContext.getContentResolver();
        mView = this; 

        mAdapter = new RecipientsCursorAdapter(mContext, 0, null, new String[0], new int[0], mContext);

        mAdapter.setCursorToStringConverter(new CursorToStringConverter() {
            @Override
            public CharSequence convertToString(Cursor c) {
                String contactName = c.getString(c.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));
                return contactName;
            }
        });

        addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                Log.d(DEBUG_TAG, "onTextChanged()");
                if (!s.equals(""))
                    mCurFilter = s.toString();
                else
                    mCurFilter = "";

                mContext.getLoaderManager().restartLoader(0, null, mView);

            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });

        setAdapter(mAdapter);
        setTokenizer(new SpaceTokenizer());

        mContext.getLoaderManager().initLoader(0, null, this);

    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created. This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        Log.d(DEBUG_TAG, "onCreateLoader()");
        Uri baseUri;
        if (mCurFilter != null) {
            baseUri = Uri.withAppendedPath( ContactsContract.Contacts.CONTENT_FILTER_URI,Uri.encode(mCurFilter));
        } else {
            baseUri = ContactsContract.Contacts.CONTENT_URI;
        }

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        String selection = "((" + ContactsContract.Contacts.DISPLAY_NAME
                + " NOTNULL) AND ("
                + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
        String sortOrder = ContactsContract.Contacts.DISPLAY_NAME
                + " COLLATE LOCALIZED ASC";

        return new CursorLoader(mContext, baseUri, CONTACTS_SUMMARY_PROJECTION,
                selection, null, sortOrder);
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Swap the new cursor in. (The framework will take care of closing
        // the old cursor once we return.)
        Log.d(DEBUG_TAG, "onLoadFinished()");
        mAdapter.swapCursor(data);

    }

    public void onLoaderReset(Loader<Cursor> loader) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed. We need to make sure we are no
        // longer using it.
        Log.d(DEBUG_TAG, "onLoaderReset()");
        mAdapter.swapCursor(null);
    }

    private class SpaceTokenizer implements Tokenizer {

        public int findTokenStart(CharSequence text, int cursor) {
            int i = cursor;

            while (i > 0 && text.charAt(i - 1) != delimiter) {
                i--;
            }
            while (i < cursor && text.charAt(i) == delimiter) {
                i++;
            }

            return i;
        }

        public int findTokenEnd(CharSequence text, int cursor) {
            int i = cursor;
            int len = text.length();

            while (i < len) {
                if (text.charAt(i) == delimiter) {
                    return i;
                } else {
                    i++;
                }
            }

            return len;
        }

        public CharSequence terminateToken(CharSequence text) {
            Log.d(DEBUG_TAG, "terminateToken()");
            int i = text.length();
            while (i > 0 && text.charAt(i - 1) == delimiter) {
                i--;
            }

            if (i > 0 && text.charAt(i - 1) == delimiter) {
                return text;
            } else {

                CharSequence contactName = createContactBubble(text);

                return contactName;
            }
        }

    }

}
_

更新1

@Olafが提案したように、現在、setStringConversionColumn()ではなくsetCursorToStringConverter()メソッドを呼び出しています。これはCursorを実装しているため、LoaderMangerが使用できるのはこれだけなので、これをonLoadFinished()に設定しました。

_public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // Swap the new cursor in. (The framework will take care of closing
    // the old cursor once we return.)
    Log.d(DEBUG_TAG, "onLoadFinished()");   
    mAdapter.setStringConversionColumn(data.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME)); 
    mAdapter.swapCursor(data);
}
_

これは、MultiAutoCompleteTextViewに対して1つのアイテムを選択する場合に機能しますが、MultiAutoCompleteTextViewで複数のアイテムを選択することはできません。

onTextChanged()を呼び出すため、restartLoader()メソッドに問題があると思います。これは、このビューの最初のエントリでは機能しますが、後続のエントリでは機能しません。現時点では何が悪いのかよくわかりません。

更新2

だから私は問題を特定しました。問題は、TextWatcherのonTextChanged()メソッドです。最初のトークンを終了するように選択した後(トークンが「JoeJohnson」だったとしましょう)、このMultiAutoCompleteTextViewalなど)にさらに文字を入力すると、onTextChanged()メソッドに渡されるargsの値になります。追加された文字だけでなく、以前に終了したトークンの文字も含まれるようになりました(この時点でのsの値は_Joe Johnson al_です)。これで、mCursorの値が_Joe Johnson al_に設定され、その後onCreateLoader()のクエリに渡され、明らかに結果が返されません。この状況を回避する方法はありますか?私はどんな提案にもオープンです。

更新

MultiAutoCompleteTextViewに裏打ちされたカスタムSimpleCursorAdapterをカスタムTokenizerと一緒に実装したとき、FilterQueryProviderを次のように設定しました。

_mAdapter.setFilterQueryProvider(new FilterQueryProvider() {
    @Override
    public Cursor runQuery(CharSequence constraint) {
    Log.d(DEBUG_TAG, "runQuery() : constraint " + constraint);
        Uri baseUri;
        if (constraint != null) {
            baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,
                Uri.encode(constraint.toString()));
        } else {
            baseUri = ContactsContract.Contacts.CONTENT_URI;
            }

        String selection = "((" + ContactsContract.Contacts.DISPLAY_NAME
            + " NOTNULL) AND ("
            + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
            + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";

        final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME};
        String sortOrder = ContactsContract.Contacts.DISPLAY_NAME
            + " COLLATE LOCALIZED ASC";

        Cursor c = mContentResolver.query(baseUri,
    CONTACTS_SUMMARY_PROJECTION, selection, null, sortOrder);
        return c;
    }
});
_

そして、何らかの理由で、runQuery()メソッドがTextWatcherのonTextChanged()メソッドから2回呼び出されます。

_public void onTextChanged(CharSequence s, int start, int before,
                int count) {
    Log.d(DEBUG_TAG, "onTextChanged()  : s " + s);
    mAdapter.getFilterQueryProvider().runQuery(s);
}
_

したがって、前の例では、最初にrunQuery()メソッドに渡されるconstraint変数は_Joe Johnson al_です。次に、2回目のrunQuery()メソッドが呼び出されると、constraint変数の値はalになります。 runQuery()メソッドで1回だけ呼び出されたのに、なぜonTextChanged()メソッドが2回実行されるのかわかりません。

33
toobsco42

基本的に、androidのオートコンプリートテキストビューはそれほど強力ではありません。大量のデータを処理する必要がある場合は、テキスト変更リスナーを編集テキストに保持して検索し、編集テキストで何かが変更されるたびに、データベースにクエリを実行します。

これが誰かを助けるかもしれないなら、編集テキストをonCreateに置いてください

EditText etSearch = (EditText)findViewById(R.id.etSearchBox);
etSearch.addTextChangedListener(filterTextWatcher);

//The filterTextWatcher is 

private TextWatcher filterTextWatcher = new TextWatcher() {
     @Override
    public void afterTextChanged(Editable s) {
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count,int after) {
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before,int count) {
        adapter.getFilter().filter(s.toString());
        }
    };  

したがって、アダプタで、getFilter()メソッドを作成する必要があります...

@Override
    public Filter getFilter() {
    if (nameFilter == null) {
        nameFilter = new NameFilter();
    }
    return nameFilter;
}

    private class NameFilter extends Filter {

    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
    FilterResults results = new FilterResults();
    Cursor cursor = null;
    // get your cursor by passing appropriate query here
    results.values = cursor;
    results.count = cursor.getCount();
    return results;
    }

    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
    notifyDataSetChanged();
        }
    }
3
Bijay Koirala