Wie verwende ich AutoCompleteTextView und fülle es mit Daten aus einer Web-API?

82

Ich möchte AutoCompleteTextViewin meiner Aktivität ein verwenden und die Daten während der Benutzertypen durch Abfragen einer Web-API auffüllen. Wie mache ich das?

Erstelle ich eine neue Klasse und überschreibe sie AutoCompleteTextView.performFilteringoder verwende ich einen benutzerdefinierten android.widget.FilterListenadapter und stelle eine benutzerdefinierte Klasse bereit , die performFiltering überschreibt?

Oder gibt es einen besseren Weg, um mein Endziel zu erreichen?

Ich habe etwas Ähnliches getan, aber es war für das Feld Schnellsuche und es beinhaltete die Implementierung eines Dienstes, aber ich glaube, das ist nicht das, was ich hier tun möchte.

AJ.
quelle
10
Ein großartiger Link für zukünftige Zuschauer :): makovkastar.github.io/blog/2014/04/12/…
Tina

Antworten:

103

Ich habe eine Lösung gefunden, ich weiß nicht, ob es die beste Lösung ist, aber sie scheint sehr gut zu funktionieren. Ich habe einen benutzerdefinierten Adapter erstellt, der ArrayAdapter erweitert. Im benutzerdefinierten Adapter habe ich getFilter überschrieben und meine eigene Filterklasse erstellt, die performFiltering überschreibt. Dadurch wird ein neuer Thread gestartet, damit die Benutzeroberfläche nicht unterbrochen wird. Unten sehen Sie ein Barebone-Beispiel.

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;
    }
}
AJ.
quelle
4
Gute Lösung - genau das, was ich brauchte. Wenn ich Sie fragen darf ... geben Sie einen Stil zu Ihrem simple_dropdown_item_1line-Layout zurück. Wie können Sie einen geeigneten Wert aus Ihrer Style-Klasse abrufen? Für mich ist es eine Klasse, die ich erstellt habe, und ich möchte, dass der Textwert getStyleName in meinem Listenelement angezeigt wird, aber es zeigt einfach den Namen der Klasse an.
Bugfixr
7
@bugfixr Fügen Sie Ihrer Klasse eine öffentliche toString-Methode hinzu. In meinem Beispiel war es: public String toString () {return name; }
AJ.
1
Seien Sie vorsichtig mit dem Aufruf notifyDataSetInvalidated (). Das bedeutet, dass der Adapter anschließend nicht verwendet werden kann.
Vikram Bodicherla
1
Dies führt jedoch eine Netzwerkanforderung im selben Thread aus. Wird die performFilteringMethode in einem Thread ohne Benutzeroberfläche ausgeführt?
Bhargav
2
@Tohid Dies ist immer noch die beste Antwort, nur eines kann hinzugefügt werden. Das heißt, ein verzögerter Handler könnte verwendet werden, um den API-Aufruf auszulösen. Wie hier gezeigt: truiton.com/2018/06/…
KnowIT
7

Aufbauend auf AJ. In der obigen Antwort enthält der folgende benutzerdefinierte Adapter auch die Verarbeitung der Serveranforderungen und das JSON-Parsing:

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

und benutze es genauso:

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);
    }
}
Razz
quelle
3

Chu: Gehen Sie wie folgt vor, um das Erscheinungsbild der Ansicht anzupassen und mehr Kontrolle über das Auspacken des Objekts zu erhalten ...

    @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;
    }
Daniel
quelle
2
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();
    }
}
Ketan Ramani
quelle
1
Stimmen Sie für actvName.isPerformingCompletion () ab, das meine Behandlung gerettet hat.
Rajat Mehra
1

Hier ist eine Kotlin-Version der Adapterklasse, die Daten aus einer lokalen Datenbank über Room lädt:

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

Definition der DAO-Methode:

    @Query("select * from SearchResult where full_name like :name and type = 'USER_TYPE'")
fun findWithNameLike(name: String): List<SearchResult>
Innova
quelle
Vielen Dank! Du hast mir gerade ein paar Stunden gespart =)
Barakuda