Android 强制BaseAdapter更新它';在异步任务完成时创建视图
好的,我的方法是这样的。我正在对Android 强制BaseAdapter更新它';在异步任务完成时创建视图,android,android-listview,android-asynctask,baseadapter,Android,Android Listview,Android Asynctask,Baseadapter,好的,我的方法是这样的。我正在对活动中的异步任务中本地存储的加密和序列化对象进行解码。活动使用BaseAdapter(HistoryAdapter)显示数据。AsyncTask显示一个ProgressDialog,直到解码完成。首次调用onProgressUpdate()时,将取消ProgressDialog。到目前为止,一切顺利。接下来,在onProgressUpdate()中,HistoryAdapter将以通用方式收到更改通知,从而触发其getView()方法。在HistoryAdapte
活动
中的异步任务
中本地存储的加密和序列化对象进行解码。活动使用BaseAdapter
(HistoryAdapter)显示数据。AsyncTask
显示一个ProgressDialog
,直到解码完成。首次调用onProgressUpdate()
时,将取消ProgressDialog
。到目前为止,一切顺利。接下来,在onProgressUpdate()
中,HistoryAdapter将以通用方式收到更改通知,从而触发其getView()
方法。在HistoryAdapter的getView()
中,运行第二个AsyncTask
来修改创建的convertView
,并将数据设置到视图上
这就是我失败的地方。我在onProgressUpdate()
中为最终布局充气,并在convertView
上设置属性和数据。即使所有数据都已设置,更改也不会显示
因此,HistoryAdapter中的AsyncTask
本身实际上工作得很好,只是变化不可见。我尝试了上面提到的许多建议,比如使convertView
无效,将引用传递到ListView
并使用invalidateViews()
(导致一个永恒的循环,但没有可见的更改,这是有意义的)
我真的希望这样,因为我真的不想在数据可用之前加载带有图像占位符的布局。我开始工作了,但看起来很糟糕,好像是一条简单的出路。因此,我需要列表视图
仅在进度完成时更新(添加项目)。有什么想法吗
编辑:澄清:数据是在正确的时间在适配器上设置的。问题是,适配器首先创建一个空白的视图
(占位符)(不知道任何其他方法,否则您将在getView中得到一个NullPointerException),然后这个视图
被另一个视图
膨胀/替换为onProgressUpdate()
中的另一个视图。第二个视图是应该可见的视图。这在某种程度上起作用,因为我可以获取和设置新膨胀视图的属性。变化是不可见的,我仍然看到空白,最初创建的视图。我想在每个添加的项目上更新ListView,而不是在所有项目加载完毕后
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
//convertView = mInflater.inflate(R.layout.history_list_item_holo_dark, null);
convertView = mInflater.inflate(R.layout.blank, parent, false); // CHEAT: LOAD BLANK/ EMPTY LAYOUT
HistoryHolder item = history.get(position);
new AsyncRequest(convertView, position).execute(item);
}
this.parent = parent;
return convertView;
}//end method
static class ViewHolder {
TextView TITLE;
TextView SUMMARY;
TextView DATE;
ImageView CONTACT_ICON;
ImageView OPTIONS_ICON;
ImageView TYPE_ICON;
}//end class
private class AsyncRequest extends AsyncTask<HistoryHolder, View, View> {
ViewHolder holder = null;
String title = "";
String summary = "";
String date = "";
long id = 0;
private View convertView = null;
private String type = "";
private int position = -1;
public AsyncRequest(View superView, int position){
this.convertView = superView;
this.position = position;
}//end constructor
@Override
protected View doInBackground(HistoryHolder... item) {
Thread.currentThread().setName(getClass().getSimpleName());
if (item[0].TYPE.equals("log")){
//massive decrypting going on here 50-100 ms
//values like title and summray set here
}
if (item[0].TYPE.equals("sms")){
//massive decrypting going on here 50-100 ms
//values like title and summray set here
}
publishProgress(convertView);
return convertView;
}// end method
@Override
protected void onProgressUpdate(View... view) {
super.onProgressUpdate(view);
}// end method
@Override
protected void onPostExecute(View result) {
super.onPostExecute(result);
if (!MathUtils.isEven(position)){
result .setBackgroundColor(context.getResources().getColor(R.color.darker)); //this works as expected, list items vary in color
} else {
result .setBackgroundColor(context.getResources().getColor(R.color.medium_dark));
} //this works as expected, list items vary in color
result = mInflater.inflate(R.layout.history_list_item_holo_dark, parent, false);
result.setTag(id);
holder = new ViewHolder();
holder.TITLE = (TextView) result .findViewById(R.id.title);
holder.SUMMARY = (TextView) result .findViewById(R.id.summary);
holder.DATE = (TextView) result .findViewById(R.id.date);
holder.CONTACT_ICON = (ImageView) result .findViewById(R.id.icon);
holder.TYPE_ICON = (ImageView) result .findViewById(R.id.type);
holder.OPTIONS_ICON = (ImageView) result .findViewById(R.id.options);
holder.OPTIONS_ICON.setFocusable(false);
holder.OPTIONS_ICON.setTag(id);
holder.TITLE.setText(title); //this change doesnt show
holder.SUMMARY.setText(summary); //and so on
result .setTag(holder);
}//end method
}//end inner class
首先,当异步任务运行onPostExecute时,必须将数据设置为适配器和notifydatasetchanged()。
现在,当列表视图为空时,使用诸如创建progressBar之类的方法将其设置为listview,如下所示:
listView.setEmptyView(progressbar);
你需要分开你的顾虑。解密/解码数据在UI层中没有位置
一般来说,您当前对每个项目执行异步任务的方法很困难,而且(有些人会说)由于多种原因是错误的:
在Honeycomb和更高版本中,在任何一个时间点上都只有一个AsyncTask在运行,除非您显式地设置了执行器
更重要的是,通过持有对convertView
的引用,您正在从ListView泄漏抽象-您持有引用的视图有可能被重新用于其他位置。除非您煞费苦心地取消异步任务并确保正确的结果交付,否则这将给您带来麻烦
正如上面提到的那样,解密/解码数据在UI层中没有位置(我考虑适配器UI层,因为它们对执行速度有类似的限制)。
如果我是你,我会使用解密数据的内存缓存,随着需求的变化而扩展/缩小。然后,我将在适配器的getView()
方法中获取解密数据。为了避免滚动时解密项目,您可以在列表视图
上设置滚动侦听器,以便仅在列表未移动时显示项目。ApiDemos中有一个类似的演示
编辑:
至于您的明显问题,您正在重新展开视图(result
),而没有将其添加到列表项(任务中的convertView
字段)。您可以通过将其添加到convertView
(例如,在空布局中)来修复此问题。同样,这不会在所有情况下都像您预期的那样起作用。Dmmh,您所说的,您想要做的事情(因此我需要ListView更新(添加项目),只有在进度完成时)和您正在做的事情(getView中的AsyncTask)完全相反
getView末尾的AsyncTask用于延迟图像加载(但方式不同),即显示大图像和显示文本+图像占位符,直到下载完成
您正试图在第一个异步任务中逐渐填充适配器的数据源,但每次您通知观察者dataset中的更改时,dataset中的每个项都将有另一个getView调用周期。不好
首先,永远不要,永远不要假设getView将为您提供一个convertview,该视图以前是为这个职位填写的。所以,您必须使用新值重新填充convert视图,或者关闭性能优化并在每次需要时提供新视图。ListView无法关闭回收尝试,因为这是ListView的本质,它是基于此构建的
第二个(源于第一个),尽量避免将耗时(或用户输入)的数据存储到新创建的视图中。视图来来往往,您不想走很长的路来获取昂贵的数据(或者只是丢失用户输入)。唯一的部分排除是大映像延迟加载的简单无效实现。为了减少内存使用,他们只下载用户当前可见的图像。更有效的实现使用离线列表视图缓存
所以,如果你真的想哈
listView.setEmptyView(progressbar);
class DecryptedRecord {
String title = "";
String summary = "";
String date = "";
int contactViewIndex = -1;
int contactOptionsIndex = -1;
int contactImageIndex = -1;
int typeImageIndex = -1;
int optionsImageIndex = -1; //if option icon varies. ignore otherwise
}
public void getView(){
result = mInflater.inflate(R.layout.history_list_item_holo_dark, parent, false);
holder = new ViewHolder();
holder.TITLE = (TextView) result .findViewById(R.id.title);
holder.SUMMARY = (TextView) result .findViewById(R.id.summary);
..
holder.OPTIONS_ICON.setTag(id);
result.setTag(holder);
result.setTag(R.id.view_id ,id);
AsyncTask.execute(result);
convertView = result;
}
AsyncTask(View ...){
onPostExecute(View view){
ViewHolder holder = view.getTag();
if(holder != null){
//set visibility of views in holder
//update Text & data
}
}
}