由 CursorLoader 支持的 AutoCompleteTextView [英] AutoCompleteTextView backed by CursorLoader

查看:23
本文介绍了由 CursorLoader 支持的 AutoCompleteTextView的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我无法扩展 MultiAutoCompleteTextView 并使用 CursorLoader 支持它,同时使用自定义 Tokenizer.这个问题特别出现在 mAdapter.setCursorToStringConverter(); 调用中.以 Cursor 作为参数的 convertToString() 方法在第一次调用此方法时具有有效且未关闭的游标.但是,后续调用会导致空游标或关闭游​​标.我猜这与 LoaderManager 管理 CursorLoader 的方式有关.

So I am having trouble extending the MultiAutoCompleteTextView and backing it with a CursorLoader, while simultaneously using a custom Tokenizer. The issue rises specifically with the mAdapter.setCursorToStringConverter(); call. The convertToString() method which has a Cursor as an argument has a valid and unclosed cursor upon the first call to this method. However subsequent calls result in either a null cursor or a closed cursor. I am guessing this has something to do with how the LoaderManager manages the CursorLoader.

如果我将 setCursorToStringConverter() 方法注释掉,那么我确实会看到基于我在此视图中输入的文本的可用选项列表.但是,由于没有实现 convertToString() 方法,那么自定义 TokenizerterminateToken() 方法不会收到我想要它,而是游标对象的代表性字符串,因为游标尚未用于获取结果查询中所需列的当前字符串值.

If I comment the setCursorToStringConverter() method out, then I do see a list of available choices based on the text I entered into this view. However, since there is no convertToString() method implemented, then the terminateToken() method of the custom Tokenizer does not receive the string that I intend it to be, but rather a representative string of the cursor object, since the cursor has not been used to get the current string value of a desired column in the resulting query.

有没有人能够实现三个类(CursorLoader/LoaderMangerMultiAutoCompleteTextViewTokenizer)的组合?

Has anyone been able to implement the combination of the three classes (CursorLoader/LoaderManger, MultiAutoCompleteTextView, and Tokenizer) ?

我这样做是朝着正确的方向前进,还是根本不可能?

Am I going in the right direction with this, or is this simply not possible?

我已经能够实现由 SimpleCursorAdapter 和自定义 Tokenizer 支持的自定义 MultiAutoCompleteTextView.我只是想知道是否可以使用 CursorLoader 来实现这一点,因为严格模式抱怨 MultiAutoCompleteTextView 中的光标没有被明确关闭.

I have been able to implement a custom MultiAutoCompleteTextView backed by a SimpleCursorAdapter along with a custom Tokenizer. I was just wondering if its possible to implement this using a CursorLoader instead, since Strict Mode complains about the cursor in MultiAutoCompleteTextView not being explicitly closed.

任何帮助将不胜感激.

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

我现在正在调用 setStringConversionColumn() 方法而不是 @Olaf 建议的 setCursorToStringConverter() 方法.我已经在 onLoadFinished() 中设置了它,因为这是 Cursor 唯一可用的时间,因为它正在实现一个 LoaderManger.

I am now calling the setStringConversionColumn() method instead of the setCursorToStringConverter() as @Olaf suggested. I have set this in the onLoadFinished() since this is the only time the Cursor is available as this is implementing a LoaderManger.

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 选择一个项目,但不允许在 MultiAutoCompleteTextView 中选择多个项目.

This works in selecting one item for the MultiAutoCompleteTextView, but will not allow multiple items to be selected in the MultiAutoCompleteTextView.

我猜测 onTextChanged() 方法存在一些问题,因为它调用了 restartLoader().这适用于此视图中的第一个条目,但不适用于后续条目.我现在不太确定哪里出了问题.

I am guessing there is some issue with the onTextChanged() method since it calls restartLoader(). This works for the first entry in this view but not for subsequent entries. I'm not too sure at this point what is wrong.

更新 2

所以我已经确定了这个问题.问题在于 TextWatcher 的 onTextChanged() 方法.在选择终止第一个标记后(假设标记是 "Joe Johnson" ),然后在此 MultiAutoCompleteTextView(例如 al )中输入更多字符传递给 onTextChanged() 方法的 arg s 现在不仅包含额外添加的字符,还包含来自先前已终止的标记的字符(s 在这一点上是 Joe Johnson al ).现在 mCursor 的值被设置为 Joe Johnson al,它随后被传递到 onCreateLoader() 中的查询中,这显然不会返回任何结果.有什么办法可以解决这种情况吗?我愿意接受任何建议.

So I have identified the issue. The problem is the TextWatcher's onTextChanged() method. After making the selection to terminate the first token ( let's say the token was "Joe Johnson" ), then entering more characters into this MultiAutoCompleteTextView ( such as al ) the value of the arg s that gets passed into the onTextChanged() method now contains not only the additionally added characters but also the characters from the token which has previously been terminated ( the value of s at this point is Joe Johnson al ). Now the value of mCursor gets set to Joe Johnson al which subsequently gets passed into the query in onCreateLoader() which will obviously return no results. Are there any ways around this situation? I am open to any suggestions.

更新 3

当我实现由 SimpleCursorAdapter 支持的自定义 MultiAutoCompleteTextView 以及自定义 Tokenizer 时,我设置了一个 FilterQueryProvider像这样:

When I implemented a custom MultiAutoCompleteTextView backed by a SimpleCursorAdapter along with a custom Tokenizer I set a FilterQueryProvider like this:

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() 方法调用了两次:

And for some reason the runQuery() method gets called twice from the TextWatcher's onTextChanged() method:

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.然后第二次调用runQuery()方法,constraint变量的值为al.我不知道为什么 runQuery() 方法在 onTextChanged() 方法中只调用一次时会运行两次.

So in my previous example, the constraint variable that gets passed into the runQuery() method the first time is Joe Johnson al. Then the second time runQuery() method is called the value of the constraint variable is al. I don't know why the runQuery() method runs twice when its only called once in the onTextChanged() method.

推荐答案

基本上,androids autocomplete textview 不是很强大,当我必须处理大量数据时,我所做的是,我保留一个文本更改侦听器以用于搜索的编辑文本,然后每当编辑文本上的某些内容发生更改时,它就会查询数据库.

Basically, androids autocomplete textview is not very powerful, when I have to deal with bigger amounts of data, what I do is, i keep a text change listener to the edit text for search, and then whenever something is changed on the edit text, it queries database.

如果这对某人有帮助,请在 onCreate 上放置一个编辑文本

If this might help someone, placing an edittext on 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() 方法...

So, in your adapter, you need to create a getFilter() method...

@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();
        }
    }

这篇关于由 CursorLoader 支持的 AutoCompleteTextView的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆