How to update some data in a Listview without using notifyDataSetChanged()?

廉价感情. 提交于 2019-11-28 03:51:13

Of course, As pjco stated, do not update at that speed. I would recommend sending broadcasts at intervals. Better yet, have a container for the data such as progress and update every interval by polling.

However, I think it is a good thing as well to update the listview at times without notifyDataSetChanged. Actually this is most useful when the application has a higher update frequency. Remember: I am not saying that your update-triggering mechanism is correct.


Solution

Basically, you will want to update a particular position without notifyDataSetChanged. In the following example, I have assumed the following:

  1. Your listview is called mListView.
  2. You only want to update the progress
  3. Your progress bar in your convertView has the id R.id.progress

public boolean updateListView(int position, int newProgress) {
    int first = mListView.getFirstVisiblePosition();
    int last = mListView.getLastVisiblePosition();
    if(position < first || position > last) {
        //just update your DataSet
        //the next time getView is called
        //the ui is updated automatically
        return false;
    }
    else {
        View convertView = mListView.getChildAt(position - first);
        //this is the convertView that you previously returned in getView
        //just fix it (for example:)
        ProgressBar bar = (ProgressBar) convertView.findViewById(R.id.progress);
        bar.setProgress(newProgress);
        return true;
    }
}

Notes

This example of course is not complete. You can probably use the following sequence:

  1. Update your Data (when you receive new progress)
  2. Call updateListView(int position) that should use the same code but update using your dataset and without the parameter.

In addition, I just noticed that you have some code posted. Since you are using a Holder, you can simply get the holder inside the function. I will not update the code (I think it is self-explanatory).

Lastly, just to emphasize, change your whole code for triggering progress updates. A fast way would be to alter your Service: Wrap the code that sends the broadcast with an if statement which checks whether the last update had been more than a second or half second ago and whether the download has finished (no need to check finished but make sure to send update when finished):

In your download service

private static final long INTERVAL_BROADCAST = 800;
private long lastUpdate = 0;

Now in doInBackground, wrap the intent sending with an if statement

if(System.currentTimeMillis() - lastUpdate > INTERVAL_BROADCAST) {
    lastUpdate = System.currentTimeMillis();
    Intent intent_progress = new Intent(ACTION_UPDATE_PROGRESS);
    intent_progress.putExtra(KEY_SAVEDSHOW_ID, savedShow.getId());
    intent_progress.putExtra(KEY_PROGRESS, downloaded );
    LocalBroadcastManager.getInstance(DownloadService.this).sendBroadcast(intent_progress);
}

The short answer: dont update the UI based on data speeds

Unless you are writing a speed test style app, there is no user benefit to updating this way.

ListView is very well optimized, (as you seem to already know because you are using the ViewHolder pattern).

Have you tried calling notifyDataSetChanged() every 1 second?

Every 1024 bytes is ridiculously fast. If someone is downloading at 8Mbps that could be updating over 1000 times a second and this could definitely cause an ANR.

Rather than update progress based on amount downloaded, you should poll the amount at an interval that does not cause UI blocking.

Anyways, in order to help avoid blocking the UI thread you could post updates to a Handler.

Play around with the value for sleep to be sure you are not updating too often. You could try going as low as 200ms but I wouldn't go below 500ms to be sure. The exact value depends on the devices you are targeting and number of items that will need layout passes.

NOTE: this is just one way to do this, there are many ways to accomplish looping like this.

private static final int UPDATE_DOWNLOAD_PROGRESS = 666;

Handler myHandler = new Handler()
{
    @Override
    handleMessage(Message msg)
    {
        switch (msg.what)
        {
            case UPDATE_DOWNLOAD_PROGRESS:
                myAdapter.notifyDataSetChanged();
                break;
            default:
                break;
        }
    }
}



private void runUpdateThread() { 
    new Thread(
     new Runnable() {
         @Override
         public void run() {
             while ( MyFragment.this.getIsDownloading() )
             {
                  try 
                  {    
                      Thread.sleep(1000); // Sleep for 1 second

                      MyFragment.this.myHandler
                          .obtainMessage(UPDATE_DOWNLOAD_PROGRESS)
                          .sendToTarget();
                  } 
                  catch (InterruptedException e) 
                  {
                      Log.d(TAG, "sleep failure");
                  }
             }

         }
     } ).start(); 
}

Although its not an answer to your question but one optimization that can be done in your getView() method is this, Instead of creating and setting click listener every time like this:

holder.downloadStateBtn.setTag(position); 
holder.downloadStateBtn.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) { 
            int position = (Integer) v.getTag(); 
             // your current normal click handling
        }
    });

You can just create it once as class variable and set it while creating row's View:

final OnClickListener btnListener = new OnClickListener() {

    @Override
    public void onClick(View v) { 
        int position = (Integer) v.getTag();
        // your normal click handling code goes here
    }
}

and then in getView():

 if (v == null) {
        v = mLayoutInflater.inflate(R.layout.saved_show_list_item, parent, false);
        // your ViewHolder stuff here 
        holder.downloadStateBtn.setOnClickListener(btnClickListener);//<<<<<
        v.setTag(holder);
    } else {
        holder = (ViewHolder) v.getTag();
    }

oh and don't forget to set tag on this button in getView() as you are already doing:

holder.downloadStateBtn.setTag(position);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!