6

这个想法是有一个项目列表,在单击项目后,进度条将随着任务完成而慢慢填充。例如,描绘一个文件列表,每个文件都有一个下载按钮。单击下载按钮时,文件会在后台下载,并且会填充一个进度条,显示文件距离完成有多近。

为此,我创建了一个 AsyncTask,它偶尔会在适配器上调用 notifyDataSetChanged 来重绘它。虽然这在单击一个按钮后有效,但在 AsyncTask 完成之前,我无法单击 ListView 中的其他按钮。有人可以告诉我我做错了什么吗?

我在 Ice Cream Sandwich 的模拟器 (x86) 上运行它。

我有一个 DownloadItem 来表示下载的进度(为简洁起见,下面的代码被简化了):

class DownloadItem {
    public String name;       // Name of the file being downloaded
    public Integer progress;  // How much is downloaded so far
    public Integer length;    // Size of the file
}

然后我有一个 ArrayAdapter,它为 ListView 调整了 DownloadItem 列表:

class DownloadArrayAdapter extends ArrayAdapter<DownloadItem> {
    List<DownloadItem> mItems;
    public DownloadArrayAdapter(List<DownloadItem> items) {
      mItems = items;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        if(row == null) {
            // Inflate
            Log.d(TAG, "Starting XML inflation");
            LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            row = inflater.inflate(R.layout.download_list_item, parent, false);
            Log.d(TAG, "Finished XML inflation");
        }

        DownloadItem item = mItems.get(position);

        ProgressBar downloadProgressBar = (ProgressBar) row.findViewById(R.id.downloadProgressBar);
        Button downloadButton = (Button) row.findViewById(R.id.downloadButton);

        downloadButton.setTag(item);
        downloadProgressBar.setMax(item.length);
        downloadProgressBar.setProgress(item.progress);

        return row;
    }
}

到目前为止,一切都很好 - 这可以正确呈现列表。在我的活动中,我有 onClickListener:

class DownloadActivity extends Activity {
    //...
    public void onDownloadButtonClick(View view) {
        DownloadItem item = (DownloadInfo)view.getTag();
        DownloadArrayAdapter adapter = (DownloadArrayAdapter) view.getAdapter();
        new DownloadTask(adapter, item).execute();
        //new DownloadTask(adapter, item).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
    }
}

我已经用 executeOnExecutor 尝试过这个,也只是执行,但没有运气。下载任务是:

class DownloadTask extends AsyncTask<Void, Integer, Void> {
    ArrayAdapter<?> mAdapter;
    DownloadItem mItem;

    public DownloadTask(ArrayAdapter<?> adapter, DownloadItem item) {
        mItem = item;
    }

    //Dummy implementation
    @Override
    public Void doInBackground(Void ... params) {
        for(int i=0; i<mItem.length; ++i) {
            Thread.sleep(10); publishProgress(i);
        }
        return null;
    }

    @Override
    public void onProgressUpdate(Integer ... values) {
        mItem.progress = values[0];
        mAdapter.notifyDataSetChanged();
    }
}

这有效 - 几乎。当我这样做时,单击一个按钮后,ProgressBar 会正常更新 - 但在 AsyncTask 返回之前,我无法单击 ListView 中的其他按钮。也就是说,永远不会调用 onDownloadButtonClick。如果我从 onProgressUpdate 函数中删除 mAdapter.notifyDataSetChanged() 调用,多个任务会同时更新,但当然,列表不会失效,因此我必须滚动查看更改。

我做错了什么,我该如何解决这个问题?

编辑:我又玩了一些,似乎调用 notifyDataSetChanged 的​​频率会影响 onClick 是被调用还是丢失。使用上面的代码,通过疯狂的点击,我偶尔可以得到第二个下载栏来启动。如果我将 Thread.Sleep 增加到更大的值,例如 2000,则该列表将按我最初的预期工作。

所以新问题 - 我如何让 ProgressBars 顺利更新而不阻止 onClick 被调用?

编辑#2:我已将一个有此问题的示例项目推送到我的 GitHub 帐户:https ://github.com/mdkess/ProgressBarListView

4

2 回答 2

6

我已经想通了。

我没有调用 notifyDataSetChanged(),而是在 DownloadItem 对象中存储了对每个 ProgressBar 的引用。然后,当滚动 ListView 时,当旧对象作为 convertView 传递时,我从旧的 DownloadInfo 中删除了 ProgressBar 并将其放在新的上。

因此,我的数组适配器的 getView 就变成了:

 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
    View row = convertView;
    final DownloadInfo info = getItem(position);
    // We need to set the convertView's progressBar to null.

    ViewHolder holder = null;

    if(null == row) {
      LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
      row = inflater.inflate(R.layout.file_download_row, parent, false);

      holder = new ViewHolder();
      holder.textView = (TextView) row.findViewById(R.id.downloadFileName);
      holder.progressBar = (ProgressBar) row.findViewById(R.id.downloadProgressBar);
      holder.button = (Button)row.findViewById(R.id.downloadButton);
      holder.info = info;

      row.setTag(holder);
    } else {
      holder = (ViewHolder) row.getTag();

      holder.info.setProgressBar(null);
      holder.info = info;
      holder.info.setProgressBar(holder.progressBar);
    }

    holder.textView.setText(info.getFilename());
    holder.progressBar.setProgress(info.getProgress());
    holder.progressBar.setMax(info.getFileSize());
    info.setProgressBar(holder.progressBar);

    holder.button.setEnabled(info.getDownloadState() == DownloadState.NOT_STARTED);
    final Button button = holder.button;
    holder.button.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        info.setDownloadState(DownloadState.QUEUED);
        button.setEnabled(false);
        button.invalidate();
        FileDownloadTask task = new FileDownloadTask(info);
        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
      }
    });
    return row;
  }

然后,下载 AsyncTask 将在 progressBar 上设置进度,如果它不为空,并且这按预期工作。

我将更正后的代码上传到 GitHub,您可以在这里查看:https ://github.com/mdkess/ProgressBarListView

于 2012-07-01T20:52:11.287 回答
1

您是否尝试设置单击侦听器以在适配器本身中下载项目,如下所示:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View row = convertView;
    if(row == null) {
        // Inflate
        Log.d(TAG, "Starting XML inflation");
        LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        row = inflater.inflate(R.layout.download_list_item, parent, false);
        Log.d(TAG, "Finished XML inflation");
    }

    final DownloadItem item = mItems.get(position);

    ProgressBar downloadProgressBar = (ProgressBar) row.findViewById(R.id.downloadProgressBar);
    Button downloadButton = (Button) row.findViewById(R.id.downloadButton);

    downloadButton.setTag(item);
    downloadProgressBar.setMax(item.length);
    downloadProgressBar.setProgress(item.progress);

    downloadButton.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            new DownloadTask(DownloadArrayAdapter.this, item).execute();
        }
    });

    return row;
}
于 2012-06-26T04:22:59.543 回答