简体   繁体   中英

Android : AutoCompleteTextView listview scroll listener (for endless scrolling)



I'm using an AutoCompleteTextView to let the user of my android app search for content using a custom webservice : it's working as intended but, for now, I can't find a way to implement "Endless Scrolling" on the dropdown listview.

Right now, my AutoCompleteTextView adapter is a ArrayAdapter implementing the Filterable interface; whenever the user changes the text of the AutoCompleteTextView, the performFiltering() method of my Filter is triggered and I can make an HTTP request to my custom webservice to display appropriate content. But I would like to load more content as the user scroll the dropdown, a paging system, so I can avoid loading like hundred of results at once ... and I can't figure how to!

  • How can I get the ListView associated with the AutoCompleteTextView to implement my own OnScrollListener / EndlessScrollListener ?
  • Is there any other way ?

Thanks guys :)

My Fragment code

AutoCompleteTextView search = (AutoCompleteTextView) view.findViewById(R.id.search);

SearchAdapter searchAdapter = new SearchAdapter(getActivity(), 0);

search.setAdapter(searchAdapter);

My Adapter code (edited)

class SearchAdapter extends ArrayAdapter<Parcelable> implements Filterable {

    Integer numberPerPage = 10;
    Boolean moreDataIsAvailable = false;

    FragmentActivity activity;
    public ArrayList<Parcelable> items = new ArrayList<Parcelable>();

    public SearchAdapter(FragmentActivity a, int textViewResourceId) {
        super(a, textViewResourceId);
        activity = a;
    }

    @Override
    public int getCount() {
        return items.size();
    }

    @Override
    public Parcelable getItem(int index) {
        if(items.size() > index) {
            return items.get(index);
        } else {
            return null;
        }
    }

    @Override
    public Filter getFilter() {
        Filter filter = new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults filterResults = new FilterResults();
                if (constraint != null && constraint.toString().length() >= 3) {
                    autocomplete(constraint.toString(), items);
                    filterResults.count = items.size();
                    filterResults.values = items;
                } else {
                    items.clear();
                    filterResults.count = items.size();
                    filterResults.values = items;
                }
                return filterResults;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                if (results != null && results.count > 0) {
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
            }
        };
        return filter;
    }

    static class ViewHolder {
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder holder = null;

        View rowView = convertView;

        if (rowView == null) {

            rowView = LayoutInflater.from(activity).inflate(R.layout.search_row, parent, false);
            holder = new ViewHolder();
            rowView.setTag(holder);

        } else {

            holder = (ViewHolder) rowView.getTag();

        }

        // Setting my row data

        return rowView;

    }

    private void autocomplete(String input, ArrayList<Parcelable> items) {

        ArrayList<Parcelable> data = new ArrayList<Parcelable>();

        try {

            RequestHandler request = new RequestHandler();

            JSONObject requestParameters = new JSONObject();
            requestParameters.put("offset", 0);
            requestParameters.put("keyword", input);
            requestParameters.put("limit", numberPerPage);

            ResponseDescription response = request.request(activity, requestParameters);

            if(!response.error) {

                JSONArray searchedItems = response.getJSONArray("items");

                if(searchedItems.length() == numberPerPage) {
                    moreDataIsAvailable = true;
                } else {
                    moreDataIsAvailable = false;
                }

                for(int i = 0 ; i < searchedItems.length(); i++) {

                    JSONObject searchedItem = searchedItems.getJSONObject(i);

                    MyObject object = new MyObject();
                    object.initWithJSONObject(searchedItem);
                    data.add(object);

                }
            }

        } catch (Exception e) {

            e.printStackTrace();

        }

        items.clear();
        items.addAll(data);

    }

}

Unfortunately there's no OnScrollListener interface for autocomplete, but I think you might be able to get around that.

Here's what I think the recipe is:

  • Add another argument parameter to your autocomplete method for the offset.

I noticed you have '0' hard-coded for the offset. You'll want to be able to call autocomplete with a value for that. Also you have items.clear() at the end of autocomplete and you're only going to want to do that when the offset is zero.

  • Make an AsyncTask for your autocomplete method.

Or something with a way that you can run autocomplete in the background besides the performFiltering method in the Filter . This async task will need access to your adapter so it can add its results to the items list and call notifyDataSetChanged() just like the filter.

Your adapter will need to keep a reference to this task so you can cancel it if user starts typing again.

  • Add some logic to getView() to execute the async task.

We don't have an OnScrollListener so we'll use getView() as a sort of proxy for that.

Set a constant threshold for starting the next request. It needs to be less than your numberPerPage , so let's say '5' as an example.

Now, when the ListView calls getView() with a position that is within the threshold from the end, execute the async task. For example, after the first filter operation, you have 10 items in your list. When ListView requests a view for item 5, start the async task with an offset equal to your list size - in this case, 10 (to get items 11-20).

I'm basing this on the assumption that ListView will only request an item view if & when the user has scrolled down to that item.

  • In performFiltering() , cancel any running async task.

I have done this type of "endless" scroll, with a ListView calling an AsyncTask from OnScrollListener to return search results page by page, and I think what made it work for me is that the request response contained a total size, so I was able to use that for getCount() .

I have also used autocomplete to get remote results, but I didn't have to page those results. Matter of fact, I think I just ran the async task when the input changed and my filter methods were no-ops.

You have a different situation combining the autocomplete and the paging. You might get some stuttering behavior from scrolling the dropdown if the user reaches the end of the list before the async task can update it. So you may have to play around with the page size, the threshold and even the return value for getCount() to get an acceptable scrolling experience. But I think it's doable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM