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

后端 未结 3 1367
野的像风
野的像风 2020-12-07 16:51

I\'m trying to create a ListView with a list of downloading tasks.

The downloading tasks are managed in a Service (DownloadService). Everyt

相关标签:
3条回答
  • 2020-12-07 17:38

    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);
    
    0 讨论(0)
  • 2020-12-07 17:44

    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);
    }
    
    0 讨论(0)
  • 2020-12-07 17:48

    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(); 
    }
    
    0 讨论(0)
提交回复
热议问题