Chcę użyć AutoCompleteTextView
w mojej aktywności i wypełnić dane w miarę wpisywania przez użytkownika, wysyłając zapytanie do internetowego interfejsu API. Jak mam to zrobić?
Czy mogę utworzyć nową klasę i przesłonić AutoCompleteTextView.performFiltering
, czy też użyć niestandardowego adaptera listy i udostępnić niestandardowy, android.widget.Filter
który przesłania performFiltering?
A może jest lepszy sposób na osiągnięcie celu końcowego?
Zrobiłem coś podobnego, ale dotyczyło to okna szybkiego wyszukiwania i wymagało wdrożenia usługi, ale uważam, że nie to chcę tutaj robić.
Odpowiedzi:
Wymyśliłem rozwiązanie, nie wiem, czy jest to najlepsze rozwiązanie, ale wydaje się, że działa bardzo dobrze. To, co zrobiłem, to stworzenie niestandardowego adaptera, który rozszerza ArrayAdapter. W adapterze niestandardowym nadpisałem getFilter i utworzyłem własną klasę Filter, która przesłania performFiltering. To rozpoczyna nowy wątek, więc nie przerywa interfejsu użytkownika. Poniżej znajduje się przykład szkieletu.
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; } }
źródło
performFiltering
metoda działa w wątku bez interfejsu użytkownika?Rozszerzanie na AJ. Odpowiedzi powyżej, następujący adapter niestandardowy obejmuje również obsługę żądań serwera i analizę json:
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(); } }; }
i używaj go w ten sam sposób:
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); } }
źródło
Chu: Aby dostosować wygląd widoku i uzyskać większą kontrolę nad rozpakowywaniem obiektu, wykonaj następujące czynności ...
@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; }
źródło
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(); } }
źródło
Oto wersja klasy adaptera Kotlin, która ładuje dane z lokalnej bazy danych za pośrednictwem 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 } } }
Definicja metody DAO:
@Query("select * from SearchResult where full_name like :name and type = 'USER_TYPE'") fun findWithNameLike(name: String): List<SearchResult>
źródło