[Из песочницы] Android AutoCompleteTextView с подсказками из веб-сервиса
Для одного из своих Android-приложений Book Tracker я реализовал кастомный AutoCompleteTextView с подсказками для названий книг, которые динамически подгружаются с Google Books по мере ввода названия книги.Задача перед компонентом стояла следующая:
Загрузка данных должна осуществляться в отдельном потоке, чтобы не блокировать UI-поток; Загрузка подсказок должна начинаться только, если пользователь приостанавливает набор (чтобы предотвратить отправку множества запросов к серверу после каждого введенного символа); Подсказки должны загружаться, если пользователь ввел строку некоторой минимальной длины (нет смысла начинать загрузку данных для строки из двух или трех символов); При запросе к серверу в правой части поля должен быть показан анимированный прогресс, чтобы информировать пользователя о загрузке. Финальный результат:
Шаг 1 — реализация кастомного адаптера для AutoCompleteTextViewАдаптер для AutoCompleteTextView — это ключевой компонент, в котором происходит загрузка и хранение подсказок. BookAutoCompleteAdapter реализовывает интерфейс Filterable, чтобы перехватывать ввод пользователя из AutoCompleteTextView и передавать его в качестве поискового запроса в веб-сервис. Единственный метод интерфейса Filterable — это getFilter (), который должен возвращать экземпляр класса Filter, осуществляющий загрузку и публикацию данных. Наследники класса Filter должны реализовать два метода: performFiltering (CharSequence constraint) и publishResults (CharSequence constraint, Filter.FilterResults results).Метод performFiltering будет вызван в отдельном потоке автоматически, поэтому нет необходимости создавать и запускать новый поток вручную. Это уже сделано за разработчика в классе Filter. Метод publishResults же вызывается в UI-потоке, чтобы опубликовать результаты на экране.
BookAutoCompleteAdapter.java
public class BookAutoCompleteAdapter extends BaseAdapter implements Filterable {
private static final int MAX_RESULTS = 10;
private final Context mContext;
private List
public BookAutoCompleteAdapter (Context context) {
mContext = context;
mResults = new ArrayList
@Override public int getCount () { return mResults.size (); }
@Override public Book getItem (int index) { return mResults.get (index); }
@Override public long getItemId (int position) { return position; }
@Override public View getView (int position, View convertView, ViewGroup parent) { if (convertView == null) { LayoutInflater inflater = LayoutInflater.from (mContext); convertView = inflater.inflate (R.layout.simple_dropdown_item_2line, parent, false); } Book book = getItem (position); ((TextView) convertView.findViewById (R.id.text1)).setText (book.getTitle ()); ((TextView) convertView.findViewById (R.id.text2)).setText (book.getAuthor ());
return convertView; }
@Override
public Filter getFilter () {
Filter filter = new Filter () {
@Override
protected FilterResults performFiltering (CharSequence constraint) {
FilterResults filterResults = new FilterResults ();
if (constraint!= null) {
List
@Override
protected void publishResults (CharSequence constraint, FilterResults results) {
if (results!= null && results.count > 0) {
mResults = (List
return filter; }
/**
* Returns a search result for the given book title.
*/
private List
Шаг 3 — добавление задержки перед отправкой запроса на сервер При использовании стандартного AutoCompleteTextView запрос инициируется после каждого введенного символа. Если пользователь набирает текст без остановки, подсказки, полученные для предыдущего запроса, могут оказаться неактуальными при вводе каждого последующего символа. Это порождает ненужные и ресурсоемкие обращения к серверу, появляется шанс превышения лимитов API, которые может иметь веб-сервис, а также возвращаются устаревшие результаты, загруженные для предыдущего состояния строки запроса.Для того, чтобы избежать вышеописанных проблем, необходимо добавить небольшую задержку между вводом символа и отправкой запроса на сервер. Если во время этой задержки человек вводит следующий символ, запрос для предыдущей строки отменяется и переносится вперед на время задержки. Если же пользователь не изменяет строку на протяжении этого времени, запрос отправляется на сервер.
Чтобы реализовать вышеописанное поведение, нужно создать кастомную реализацию AutoCompleteTextView и переопределить метод performFiltering (CharSequence text, int keyCode). Поле mAutoCompleteDelay определяет время в миллисекундах, после которого запрос будет отправлен на сервер, если пользователь не ввел новых символов.
DelayAutoCompleteTextView.java public class DelayAutoCompleteTextView extends AutoCompleteTextView {
private static final int MESSAGE_TEXT_CHANGED = 100; private static final int DEFAULT_AUTOCOMPLETE_DELAY = 750;
private int mAutoCompleteDelay = DEFAULT_AUTOCOMPLETE_DELAY; private ProgressBar mLoadingIndicator;
private final Handler mHandler = new Handler () { @Override public void handleMessage (Message msg) { DelayAutoCompleteTextView.super.performFiltering ((CharSequence) msg.obj, msg.arg1); } };
public DelayAutoCompleteTextView (Context context, AttributeSet attrs) { super (context, attrs); }
public void setLoadingIndicator (ProgressBar progressBar) { mLoadingIndicator = progressBar; }
public void setAutoCompleteDelay (int autoCompleteDelay) { mAutoCompleteDelay = autoCompleteDelay; }
@Override protected void performFiltering (CharSequence text, int keyCode) { if (mLoadingIndicator!= null) { mLoadingIndicator.setVisibility (View.VISIBLE); } mHandler.removeMessages (MESSAGE_TEXT_CHANGED); mHandler.sendMessageDelayed (mHandler.obtainMessage (MESSAGE_TEXT_CHANGED, text), mAutoCompleteDelay); }
@Override public void onFilterComplete (int count) { if (mLoadingIndicator!= null) { mLoadingIndicator.setVisibility (View.GONE); } super.onFilterComplete (count); } } Шаг 4 — добавление анимированного прогресса к полю ввода Очень важно обеспечить обратную связь, когда пользователь набирает текст. Необходимо показать анимированный прогресс в поле ввода названия книги. Прогресс нужен для того, чтобы проинформировать человека о том, что подсказки загружаются и будут скоро отображены. Таким образом пользователь будет осведомлен и сможет подождать пока они не появятся. Без такой обратной связи человек может даже не подозревать о том, что поле может показывать подсказки.Элементы ProgressBar и DelayAutoCompleteTextView необходимо поместить во FrameLayout и выровнять ProgressBar по правой стороне родительской группы. Также необходимо изначально скрыть прогресс с помощью установки атрибута android: visibility=«gone».
Шаг 5 — соединение компонентов Теперь, когда все части готовы, необходимо соединить их вместе: DelayAutoCompleteTextView bookTitle = (DelayAutoCompleteTextView) findViewById (R.id.book_title); bookTitle.setThreshold (4); bookTitle.setAdapter (new BookAutoCompleteAdapter (context)); bookTitle.setLoadingIndicator ((ProgressBar) findViewById (R.id.progress_bar)); bookTitle.setOnItemClickListener (new AdapterView.OnItemClickListener () { @Override public void onItemClick (AdapterView> adapterView, View view, int position, long id) { Book book = (Book) adapterView.getItemAtPosition (position); bookTitle.setText (book.getTitle ()); } }); bookTitle.setThreshold (4) определяет минимальное количество символов, которые должен ввести пользователь, чтобы были показаны подсказки.
bookTitle.setLoadingIndicator ((ProgressBar) findViewById (R.id.progress_bar)) соединяет ProgressBar с DelayAutoCompleteTextView.
Важно установить OnItemClickListener для DelayAutoCompleteTextView и присвоить правильное значение полю ввода. Если этого не сделать, результат вызова метода toString () выбранного объекта будет вставлен в поле вместо названия книги.