Quiero usar un AutoCompleteTextView
en mi actividad y completar los datos a medida que el usuario escribe consultando una API web. ¿Cómo voy a hacer esto?
¿Creo una nueva clase y la anulo AutoCompleteTextView.performFiltering
, o uso un adaptador de lista personalizada y proporciono una personalizada android.widget.Filter
que anula performFiltering?
¿O hay una mejor manera de lograr mi objetivo final?
Hice algo similar, pero fue para el cuadro de búsqueda rápida e involucró implementar un servicio, pero creo que eso no es lo que quiero hacer aquí.
Respuestas:
Se me ocurrió una solución, no sé si es la mejor solución, pero parece funcionar muy bien. Lo que hice fue crear un adaptador personalizado que extiende ArrayAdapter. En el adaptador personalizado, anulé getFilter y creé mi propia clase Filter que anula performFiltering. Esto inicia un nuevo hilo para que no interrumpa la interfaz de usuario. A continuación se muestra un ejemplo básico.
MyActivity.java
public class MyActivity extends Activity { private AutoCompleteTextView style; @Override public void onCreate(Bundle savedInstanceState) { ... style = (AutoCompleteTextView) findViewById(R.id.style); adapter = new AutoCompleteAdapter(this, android.R.layout.simple_dropdown_item_1line); style.setAdapter(adapter); } }
AutoCompleteAdapter.java
public class AutoCompleteAdapter extends ArrayAdapter<Style> implements Filterable { private ArrayList<Style> mData; public AutoCompleteAdapter(Context context, int textViewResourceId) { super(context, textViewResourceId); mData = new ArrayList<Style>(); } @Override public int getCount() { return mData.size(); } @Override public Style getItem(int index) { return mData.get(index); } @Override public Filter getFilter() { Filter myFilter = new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults filterResults = new FilterResults(); if(constraint != null) { // A class that queries a web API, parses the data and returns an ArrayList<Style> StyleFetcher fetcher = new StyleFetcher(); try { mData = fetcher.retrieveResults(constraint.toString()); } catch(Exception e) { Log.e("myException", e.getMessage()); } // Now assign the values and count to the FilterResults object filterResults.values = mData; filterResults.count = mData.size(); } return filterResults; } @Override protected void publishResults(CharSequence contraint, FilterResults results) { if(results != null && results.count > 0) { notifyDataSetChanged(); } else { notifyDataSetInvalidated(); } } }; return myFilter; } }
fuente
performFiltering
método se ejecuta en un hilo que no es de UI?Ampliando AJ. En la respuesta anterior, el siguiente adaptador personalizado incluye el manejo de las solicitudes del servidor y el análisis json también:
class AutoCompleteAdapter extends ArrayAdapter<String> implements Filterable { private ArrayList<String> data; private final String server = "http://myserver/script.php?query="; AutoCompleteAdapter (@NonNull Context context, @LayoutRes int resource) { super (context, resource); this.data = new ArrayList<>(); } @Override public int getCount() { return data.size(); } @Nullable @Override public String getItem (int position) { return data.get (position); } @NonNull @Override public Filter getFilter() { return new Filter() { @Override protected FilterResults performFiltering (CharSequence constraint) { FilterResults results = new FilterResults(); if (constraint != null) { HttpURLConnection conn = null; InputStream input = null; try { URL url = new URL (server + constraint.toString()); conn = (HttpURLConnection) url.openConnection(); input = conn.getInputStream(); InputStreamReader reader = new InputStreamReader (input, "UTF-8"); BufferedReader buffer = new BufferedReader (reader, 8192); StringBuilder builder = new StringBuilder(); String line; while ((line = buffer.readLine()) != null) { builder.append (line); } JSONArray terms = new JSONArray (builder.toString()); ArrayList<String> suggestions = new ArrayList<>(); for (int ind = 0; ind < terms.length(); ind++) { String term = terms.getString (ind); suggestions.add (term); } results.values = suggestions; results.count = suggestions.size(); data = suggestions; } catch (Exception ex) { ex.printStackTrace(); } finally { if (input != null) { try { input.close(); } catch (Exception ex) { ex.printStackTrace(); } } if (conn != null) conn.disconnect(); } } return results; } @Override protected void publishResults (CharSequence constraint, FilterResults results) { if (results != null && results.count > 0) { notifyDataSetChanged(); } else notifyDataSetInvalidated(); } }; }
y utilícelo de la misma manera:
public class MyActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { ... AutoCompleteTextView textView = (AutoCompleteTextView) findViewById (R.id.style); int layout = android.R.layout.simple_list_item_1; AutoCompleteAdapter adapter = new AutoCompleteAdapter (this, layout); textView.setAdapter (adapter); } }
fuente
Chu: Para personalizar el aspecto de la vista y tener más control sobre cómo desenvolver el objeto, haga lo siguiente ...
@Override public View getView (int position, View convertView, ViewGroup parent) { TextView originalView = (TextView) super.getView(position, convertView, parent); // Get the original view final LayoutInflater inflater = LayoutInflater.from(getContext()); final TextView view = (TextView) inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false); // Start tweaking view.setText(originalView.getText()); view.setTextColor(R.color.black); // also useful if you have a color scheme that makes the text show up white view.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10); // override the text size return view; }
fuente
private AutoCompleteUserAdapter userAdapter; private AutoCompleteTextView actvName; private ArrayList<SearchUserItem> arrayList; actvName = findViewById(R.id.actvName); actvName.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { actvName.setText(userAdapter.getItemNameAtPosition(position)); actvName.setSelection(actvName.getText().toString().trim().length()); } }); actvName.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(final CharSequence s, int start, int before, int count) { if (actvName.isPerformingCompletion()) { // An item has been selected from the list. Ignore. } else { if (s.toString().toLowerCase().trim().length() >= 2) { getUserList(s.toString().toLowerCase().trim()); } } } @Override public void afterTextChanged(Editable s) { } }); private void getUserList(String searchText) { //Add data to your list after success of API call arrayList = new ArrayList<>(); arrayList.addAll(YOUR_LIST); userAdapter = new AutoCompleteUserAdapter(context, R.layout.row_user, arrayList); getActivity().runOnUiThread(new Runnable() { @Override public void run() { actvName.setAdapter(userAdapter); userAdapter.notifyDataSetChanged(); actvName.showDropDown(); } }); }
AutoCompleteUserAdapter
/** * Created by Ketan Ramani on 11/07/2019. */ public class AutoCompleteUserAdapter extends ArrayAdapter<SearchUserItem> { private Context context; private int layoutResourceId; private ArrayList<SearchUserItem> arrayList; public AutoCompleteUserAdapter(Context context, int layoutResourceId, ArrayList<SearchUserItem> arrayList) { super(context, layoutResourceId, arrayList); this.context = context; this.layoutResourceId = layoutResourceId; this.arrayList = arrayList; } @Override public View getView(int position, View convertView, ViewGroup parent) { try { if (convertView == null) { convertView = LayoutInflater.from(parent.getContext()).inflate(layoutResourceId, parent, false); } SearchUserItem model = arrayList.get(position); AppCompatTextView tvUserName = convertView.findViewById(R.id.tvUserName); tvUserName.setText(model.getFullname()); } catch (NullPointerException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return convertView; } public String getItemNameAtPosition(int position) { return arrayList.get(position).getName(); } public String getItemIDAtPosition(int position) { return arrayList.get(position).getId(); } }
fuente
Aquí hay una versión de Kotlin de la clase de adaptador que carga datos de una base de datos local a través de Room:
import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.Filter import android.widget.Filterable import android.widget.TextView import ...MyFinderDatabase import ...R import ...model.SearchResult class SearchCompleteAdapter(context: Context, val resourceId: Int): ArrayAdapter<SearchResult>(context, resourceId), Filterable { private val results = mutableListOf<SearchResult>() override fun getCount() = results.size override fun getItem(position: Int) = results[position] override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val view = convertView ?: LayoutInflater.from(context).inflate(resourceId, parent, false) val textView = view.findViewById<TextView>(R.id.autocomplete_name) textView.text = getItem(position).fullName return view } override fun getFilter() = object : Filter(){ override fun performFiltering(constraint: CharSequence?): FilterResults { val filterResults = FilterResults() val db = MyRoomDatabase.getDatabase(context.applicationContext) val dbResults = db.resultDao().findWithNameLike(String.format("%%%s%%", constraint.toString())) filterResults.values = dbResults filterResults.count = dbResults.size results.clear() results.addAll(dbResults) return filterResults } override fun publishResults(constraint: CharSequence?, results: FilterResults?) { if((results != null) && (results.count > 0)){ notifyDataSetChanged() } else{ notifyDataSetInvalidated() } } override fun convertResultToString(resultValue: Any?): CharSequence { val searchResult = resultValue as SearchResult return searchResult.fullName } } }
Definición del método DAO:
@Query("select * from SearchResult where full_name like :name and type = 'USER_TYPE'") fun findWithNameLike(name: String): List<SearchResult>
fuente