将ProgressBar添加到ListView / OnClick中称为一次

时间:2012-06-26 03:10:05

标签: android android-4.0-ice-cream-sandwich

想法是拥有一个项目列表,在点击项目后,ProgressBar将在任务完成时慢慢填充。例如,列出文件列表,每个文件都有一个“下载”按钮。单击下载按钮时,将在后台下载文件并填写进度条,显示文件完成的接近程度。

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

我在冰淇淋三明治上的模拟器(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;
    }
}

到目前为止,非常好 - 这正确地呈现了列表。在我的Activity中,我有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以及执行,但没有运气。 DownloadTask是:

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正常更新 - 但我不能单击ListView中的其他按钮,直到AsyncTask返回。也就是说,永远不会调用onDownloadButtonClick。如果我从onProgressUpdate函数中删除mAdapter.notifyDataSetChanged()调用,则会同时更新多个任务,但当然,列表不会失效,因此我必须滚动才能看到更改。

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

编辑:我更多地玩这个,似乎调用notifyDataSetChanged的频率会影响onClick是被调用还是只是丢失。使用上面的代码,通过疯狂点击我偶尔可以得到第二个下载栏开始。如果我将Thread.Sleep增加到更大的值,比如2000,那么列表就像我原先预期的那样工作。

所以新问题 - 如何在不阻止onClick被调用的情况下让ProgressBars顺利更新?

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

2 个答案:

答案 0 :(得分:5)

我已经弄明白了。

我没有调用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上设置进度,如果它不为null,则按预期工作。

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

答案 1 :(得分: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;
}