Bart
Bart

Reputation: 769

Control single row in ListView

I'm trying to add an animation to my ListView so text will fade into separate rows as results come in from an HTTP GET request. I know how to do the fade in effect and i already have a custom ListView adapter but the problem is that the ListView updates all the rows each time a result comes in, thus triggering the fade in effect each time for the entire list.

How would I be able to control a single row so the ListView won't animate every row on each data change?

This is the code i use to fill the ListView:

    private class CustomAdapter extends ArrayAdapter<Row> {

    public CustomAdapter() {
        super(Results.this, R.layout.row, dataList);
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        RowHolder holder = null;
        if (row == null) {
            LayoutInflater inflater = getLayoutInflater();

            row = inflater.inflate(R.layout.row, parent, false);
            holder = new RowHolder(row);
            row.setTag(holder);
        } else {
            holder = (RowHolder) row.getTag();
        }
        try {
            holder.populateRow(dataList.get(position));
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
        return row;
    }

}

    private class RowHolder {
        private TextView label = null;
        private TextView count = null;
        private TextView result = null;

        public RowHolder(View row) {
            label = (TextView) row.findViewById(R.id.list_label);
            count = (TextView) row.findViewById(R.id.list_count);
            result = (TextView) row.findViewById(R.id.list_result);
        }

        public void populateRow(Row r) {
            label.setText(r.getLabel());
            count.setText(r.getCount());
            result.setText(r.getResult());
            label.startAnimation(fadeIn);
            count.startAnimation(fadeIn);
            result.startAnimation(fadeIn);
        }
    }

Any help is appreciated, thank you in advance!

Edit 1:

My AsyncTask:

    private class CheckSource extends AsyncTask<String, Void, String> {

    protected void onPreExecute() {
        results.setUnixTime(getUnixTime());
        results.setLabel(getString(R.string.label));
        results.setCount(null);
        results.setResult(null);
        results.setResultLabel("");
        results.setShowProgress(true);
        results.setIconType(null);
        results.setShowIcon(false);
        results.setHasResults(false);
        adapter.notifyDataSetChanged();
    }

    @Override
    protected String doInBackground(String... params) {

        String query = params[0];
        String httpResults = null;

        try {
            httpResults = getResults(query, "source");
            jsonObject = new JSONObject(httpResults);
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        return httpResults;
    }

    protected void onPostExecute(String results) {
        try {
            parseJSON(results);
        } catch (JSONException e) {
            e.printStackTrace();
            results.setResultLabel("<br />"
                    + getString(R.string.source_not_available) + "<br />");
        }
        results.setShowProgress(false);
        adapter.notifyDataSetChanged();
    }

    // Parse the retrieved json results
    private void parseJSON(String jsonResults) throws JSONException {
        if (jsonResults == null) {
            results.setResult(null);
            results.setHasResults(false);
            results.setResultLabel("<br />"
                    + getString(R.string.source_not_available) + "<br />");
            return;
        }
        jsonObject = new JSONObject(jsonResults);
        String result = null;
        String resultLabel = null;
        switch (jsonObject.getInt("count")) {
        case -1:
            results.setCount(null);
            results.setHasResults(false);
            resultLabel = getString(R.string.no_results);
            break;
        case 0:
            results.setCount(null);
            results.setHasResults(false);
            resultLabel = getString(R.string.no_results);
            break;
        case 1:
            results.setHasResults(true);
            results.setCount(jsonObject.get("count").toString() + " "
                    + getString(R.string.one_result));
            result = jsonObject.get("url").toString();
            resultLabel = getString(R.string.hyperlink_text);
            break;
        default:
            results.setHasResults(true);
            results.setCount(jsonObject.get("count").toString() + " "
                    + getString(R.string.multiple_results));
            result = jsonObject.get("url").toString();
            resultLabel = getString(R.string.hyperlink_text);
            break;
        }
        results.setResult(result);
        results.setResultLabel("<br />" + resultLabel + "<br />");
    }
}

The method that executes the HTTP request:

private String getResults(String query, String source)
        throws IllegalStateException, IOException, URISyntaxException {

    /* Method variables */
    StringBuilder builder = new StringBuilder();
    String URL = "url";
    URI uri;
    String phrase = "phrase";
    List<NameValuePair> params = new ArrayList<NameValuePair>();

    /* HTTP variables */
    HttpGet httpGet;
    DefaultHttpClient httpClient;
    HttpResponse httpResponse;
    HttpEntity httpEntity;
    HttpParams httpParams;

    int socketTimeout = 10000;
    int connectionTimeout = 10000;

    // Set the socket and connection timeout values
    httpParams = new BasicHttpParams();
    HttpConnectionParams
            .setConnectionTimeout(httpParams, connectionTimeout);
    HttpConnectionParams.setSoTimeout(httpParams, socketTimeout);
    httpClient = new DefaultHttpClient(httpParams);
    // Add parameters to the GET request
    params.add(new BasicNameValuePair("query", query));
    params.add(new BasicNameValuePair("type", source));
    String paramString = URLEncodedUtils.format(params, "utf-8");
    uri = new URI(URL + paramString);
    httpGet = new HttpGet(uri);
    // Execute the GET request
    httpResponse = httpClient.execute(httpGet);

    /* Read http response if http status = 200 */
    if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
        httpEntity = httpResponse.getEntity();
        InputStream content = httpEntity.getContent();
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                content));
        String line;
        while ((line = reader.readLine()) != null) {
            builder.append(line);
        }
    }
    return builder.toString();
}

Upvotes: 1

Views: 1581

Answers (2)

GriffonRL
GriffonRL

Reputation: 2288

As Romain Guy explained a while back during the Google I/O session, the most efficient way to only update one view in a list view is something like the following (this one update the whole view data):

ListView list = getListView();
int start = list.getFirstVisiblePosition();
for(int i=start, j=list.getLastVisiblePosition();i<=j;i++)
    if(target==list.getItemAtPosition(i)){
        View view = list.getChildAt(i-start);
        list.getAdapter().getView(i, view, list);
        break;
    }

Assuming target is one item of the adapter.

This code retrieve the ListView, then browse the currently shown views, compare the target item you are looking for with each displayed view items, and if your target is among those, get the enclosing view and execute the adapter getView on that view to refresh the display.

As a side note invalidate doesn't work like some people expect and will not refresh the view like getView does, notifyDataSetChanged will rebuild the whole list and end up calling getview for every displayed items and invalidateViews will also affect a bunch.

One last thing, one can also get extra performance if he only needs to change a child of a row view and not the whole row like getView does. In that case, the following code can replace list.getAdapter().getView(i, view, list); (example to change a TextView text):

((TextView)view.findViewById(R.id.myid)).setText("some new text");

In code we trust.

Upvotes: 5

Dmytro Danylyk
Dmytro Danylyk

Reputation: 19796

Method notifyDataSetChanged force to call getView method to all visible elements of the ListView. If you want update only 1 specific item of the ListView you need to path this item to the AsynhTask.

Upvotes: 0

Related Questions