AutocompleteTextView de Android con ArrayAdapter y Filter
Información de Backgroud
La aplicación que estoy escribiendo actualmente trata de lugares. El método principal para crear un lugar es introducir una dirección. Proporcionar una manera conveniente de hacerlo es muy importante para el UX.
Solución actual
Después de investigar el problema un tiempo llegué con la conclusión de que la mejor manera de hacerlo es un área de texto con autocompletado basado en la ubicación actual y la entrada del usuario (como en la aplicación Google Map). Dado que, por desgracia, esta característica no está incluida en el API de Google Map, tengo que implementarla por mí mismo.
- AutoCompleteTextView con Google Places mostrado en ListView al igual que Uber
- Android- Problema con reemplazo de tamaño de imagen en versiones android diferentes para el texto MultiAutoCompleteTextview
- Estoy tratando de crear un AutoCompleteTextView en mi aplicación Android. El problema es que no puedo personalizarlo
- Autocompletar con nombre y número como en aplicaciones de sms nativas Android
- Java.lang.NullPointerException - AutoCompleteTextView - HoneyComb
Implementación actual
Para obtener sugerencias de direcciones, utilizo Geocoder . Estas sugerencias se proporcionan a un AutoCompleteTextView por un ArrayAdaptor personalizado a través de un filtro personalizado.
El ArrayAdapter (Se ha eliminado el código)
public class AddressAutoCompleteAdapter extends ArrayAdapter<Address> { @Inject private LayoutInflater layoutInflater; private ArrayList<Address> suggested; private ArrayList<Address> lastSuggested; private String lastInput = null; private AddressLoader addrLoader; public AddressAutoCompleteAdapter(Activity activity, AddressLoaderFactory addrLoaderFact, int maxSuggestionsCount) { super(activity,R.layout.autocomplete_address_item); suggested = new ArrayList<Address>(); lastSuggested = new ArrayList<Address>(); addrLoader = addrLoaderFact.create(10); } ... @Override public Filter getFilter() { Filter myFilter = new Filter() { ... @SuppressWarnings("unchecked") @Override protected FilterResults performFiltering(CharSequence constraint) { Log.d("MyPlaces","performFiltring call"); FilterResults filterResults = new FilterResults(); boolean debug = false; if(constraint != null) { // Load address try { suggested = addrLoader.load(constraint.toString()); } catch(IOException e) { e.printStackTrace(); Log.d("MyPlaces","No data conection avilable"); /* ToDo : put something useful here */ } // If the address loader returns some results if (suggested.size() > 0) { filterResults.values = suggested; filterResults.count = suggested.size(); // If there are no result with the given input we check last input and result. // If the new input is more accurate than the previous, display result for the // previous one. } else if (constraint.toString().contains(lastInput)) { debug = true; Log.d("MyPlaces","Keep last suggestion : " + lastSuggested.get(0).toString()); filterResults.values = lastSuggested; filterResults.count = lastSuggested.size(); } else { filterResults.values = new ArrayList<Address>(); filterResults.count = 0; } if ( filterResults.count > 0) { lastSuggested = (ArrayList<Address>) filterResults.values; } lastInput = constraint.toString(); } if (debug) { Log.d("MyPlaces","filter result count :" + filterResults.count); } return filterResults; } @Override protected void publishResults(CharSequence contraint, FilterResults results) { AddressAutoCompleteAdapter.this.notifyDataSetChanged(); } }; return myFilter; } ...
AddressLoader (se ha eliminado algún código)
public class GeocoderAddressLoader implements AddressLoader { private Application app; private int maxSuggestionsCount; private LocationManager locationManager; @Inject public GeocoderAddressLoader( Application app, LocationManager locationManager, @Assisted int maxSuggestionsCount){ this.maxSuggestionsCount = maxSuggestionsCount; this.app = app; this.locationManager = locationManager; } /** * Take a string representing an address or a place and return a list of corresponding * addresses * @param addrString A String representing an address or place * @return List of Address corresponding to the given address description * @throws IOException If Geocoder API cannot be called */ public ArrayList<Address> load(String addrString) throws IOException { //Try to filter with the current location Location current = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); Geocoder geocoder = new Geocoder(app,Locale.getDefault()); ArrayList<Address> local = new ArrayList<Address>(); if (current != null) { local = (ArrayList<Address>) geocoder.getFromLocationName( addrString, maxSuggestionsCount, current.getLatitude()-0.1, current.getLongitude()-0.1, current.getLatitude()+0.1, current.getLongitude()+0.1); } if (local.size() < maxSuggestionsCount) { ArrayList<Address> global = (ArrayList<Address>) geocoder.getFromLocationName( addrString, maxSuggestionsCount - local.size()); for (Address globalAddr : global) { if (!containsAddress(local,globalAddr)) local.add(globalAddr); } } return local; } ...
Problemas
Esta implementación funciona, pero no es perfecta.
El AddressLoader tiene un comportamiento extraño con algunas entradas.
Por ejemplo, si el usuario desea introducir esta dirección
10 rue Guy Ropartz 54600 Villers-les-Nancy
Cuando haya ingresado:
10 rue Guy
Hay algunas sugerencias pero no la dirección deseada.
Si continúa introduciendo texto cuando se alcanza esta entrada:
10 rue Guy Ro
Las sugerencias desaparecen. La dirección deseada aparece finalmente cuando la entrada es
10 rue Guy Ropart
Creo que esto es inquietante para el usuario. Es extraño que no hay ninguna sugerencia para "10 rue Guy Ro", mientras que hay sugerencias para "10 rue Guy" y "10 rue Guy Ropart" …
Una posible solución
Me imagino una solución posible para este problema y tratar de implementarlo. La idea es mantener la sugerencia anterior si después de escribir una dirección más larga no hay dirección sugerida por AdressLoader. En nuestro ejemplo mantenga "10 rue Guy" sugerencias cuando la entrada es "10 rue Guy Ro" .
Desafortunadamente mi código no funciona. Cuando estoy en esta situación se alcanza la buena parte del código:
else if (constraint.toString().contains(lastInput)) { debug = true; Log.d("MyPlaces","Keep last suggestion : " + lastSuggested.get(0).toString()); filterResults.values = lastSuggested; filterResults.count = lastSuggested.size(); }
Y FilterResult devuelto por performFiltering está lleno de las buenas sugerencias pero no aparece en la vista. Tal vez me pierda algo en publishResults …
Preguntas
- Geocoder tiene alguna vez un comportamiento extraño, tal vez hay un mejor enfoque para obtener sugerencias de direcciones?
- ¿Me puede sugerir una solución mejor que la que describí?
- ¿Cuál es el problema en la implementación de mi solución?
Actualizar
Por cuestiones de compatibilidad, implementé un nuevo AddressLoader basado en Google Geocode Http API (porque el servicio predeterminado de geocodificador de Android no está disponible en todos los dispositivos). Reproduce exactamente el mismo comportamiento extraño.
- AutoCompleteTextView detecta cuando se selecciona la entrada de la lista editada por el usuario
- AutoCompleteTextview Color establecido en blanco por defecto
- Configuración de varios elementos personalizados en MultiAutoCompleteTextView: Android
- Quitar sombra de Autodesplegable de AutoCompleteTextView
- Android AutoCompleteTextView muestra información de objeto en lugar de texto en modo horizontal
- Android - vista de búsqueda con función completa automática dentro de la barra de acción
- Cómo configurar setOnClickListener para AutoCompleteTextView?
- AutoCompleteTextView no completar palabras entre paréntesis
El Geocoder api no está diseñado para dar sugerencias sobre cadenas parciales como "10 rue Guy Ro". Usted puede probar esto en Google Maps, entrar en "10 rue Guy Ro" y dejar un espacio. No obtendrás ninguna sugerencia (Google Maps utiliza Geocoder), pero al eliminar el espacio obtendrás sugerencias sobre la cadena parcial (Google Maps utiliza la API privada). Puede utilizar el api de Geocoder de la misma manera que utiliza Google Maps, obtenga la sugerencia sólo después de que el espacio de entrada del usuario en la cadena que está escribiendo.
Intente cambiar esta parte del código:
if ( filterResults.count > 0) { lastSuggested = (ArrayList<Address>) filterResults.values; } lastInput = constraint.toString();
dentro de esto:
if ( filterResults.count > 0) { lastSuggested = (ArrayList<Address>) filterResults.values; lastInput = constraint.toString(); }
Creo que su problema es que está configurando lastSuggested a sugerido (a través de filterResults.values) y luego, en el paso siguiente, se está actualizando sugerido -> lastSuggested se establecerá al mismo valor sugerido como Java pasa variables por referencia a menos primitivas .
Entonces, en lugar de
If (filterResults.count> 0) {lastSuggested = (ArrayList) filterResults.values; }
Deberías intentarlo
if ( filterResults.count > 0) { lastSuggested.clear(); ArrayList<Address> tempList = (ArrayList<Address>) filterResults.values; int i = 0; while (i < tempList.size()){ lastSuggested.add(tempList.get(i)); i += 1; } }
- Servicio completo de música a prueba de errores de Android a través de múltiples actividades
- Android: RadioGroup – Cómo configurar el detector de eventos