想法是拥有一个项目列表,在点击项目后,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
答案 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;
}