developers developer custom android autocomplete android-arrayadapter autocompletetextview

custom - autocompletetextview android developers



Android AutocompleteTextView usando ArrayAdapter y filtro (3)

Creo que su problema es que está configurando la última sugerencia sugerida (a través de filterResults.values) y luego, en la próxima pasada, está actualizando la sugerencia -> lastSuggested se establecerá en el mismo valor sugerido que las variables Java pasa por referencia a menos que 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; } }

Backgroud informacion

La aplicación que estoy escribiendo actualmente trata con lugares. El método principal para crear un lugar es ingresar una dirección. Proporcionar una forma conveniente de hacerlo es muy importante para el UX.

Solución actual

Después de investigar el problema por un tiempo, llegué a la conclusión de que la mejor manera de hacerlo es un área de texto con autocompletado basada en la ubicación actual y la entrada del usuario (como en la aplicación Google Map). Como lamentablemente esta función no la proporciona la API de Google Map, la tengo que implementar yo mismo.

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 Filter personalizado.

El ArrayAdapter (Algún código ha sido eliminado)

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; } ...

El 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 alguna entrada.
Por ejemplo si el usuario quiere ingresar esta dirección.

10 rue Guy Ropartz 54600 Villers-les-Nancy

Cuando haya entrado:

10 rue Guy

Hay algunas sugerencias pero no la dirección deseada.
Si continúa ingresando 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 molesto para el usuario. es extraño que no haya ninguna sugerencia para "10 rue Guy Ro" mientras que hay sugerencias para "10 rue Guy" y "10 rue Guy Ropart" ...

Una posible solución

Imagino una posible solución para este problema y trato de implementarlo. La idea es mantener la sugerencia anterior si, después de escribir una dirección más larga, AdressLoader no sugiere ninguna dirección. En nuestro ejemplo, mantenga las sugerencias de "10 rue Guy" cuando la entrada sea "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 el FilterResult devuelto por performFiltering está lleno de buenas sugerencias pero no aparece en la vista. Tal vez echo de menos algo en publishResults ...

Preguntas

  • Geocoder tiene un comportamiento extraño en algún momento, ¿quizás haya 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 motivos de compatibilidad, implementé un nuevo AddressLoader basado en la API Http de Google Geocode (porque el servicio de geocodificador predeterminado de Android no está disponible en todos los dispositivos). Reproduce exactamente el mismo extraño comportamiento.


El api Geocoder no está diseñado para dar sugerencias sobre cadenas parciales como "10 rue Guy Ro". Puedes probar esto en los mapas de Google, ingresar "10 rue Guy Ro" y dejar un espacio. No recibirá ninguna sugerencia (Google maps usa Geocoder), pero cuando elimine el espacio, obtendrá sugerencias sobre la cadena parcial (Google Maps usa API privada). Puede usar la API de Geocoder de la misma manera que usa Google Maps, busque la sugerencia solo después de que el usuario ingrese el espacio en la cadena que está escribiendo.


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