好的,所以我的方法是这样的。我正在AsyncTask
Activity
中解码本地存储的加密和序列化对象。 Activity
使用BaseAdapter
(HistoryAdapter)来显示数据。 AsyncTask
显示ProgressDialog
,直到解码完成。首次调用onProgressUpdate()
时,ProgressDialog
将被取消。到现在为止还挺好。接下来,在onProgressUpdate()
中,HistoryAdapter以常用方式通知更改,触发它的getView()
方法。在HistoryAdapter的getView()
中,运行第二个AsyncTask
来修改创建的convertView
并将数据设置为View
。
这就是我失败的地方。我在onProgressUpdate()
中填充了最终版面,并在convertView
上设置了属性和数据就好了。即使所有数据都已设置,这些更改也不会显示...
因此,HistoryAdapter中的AsyncTask
本身实际上是完美的,这些变化是不可见的。我尝试了在SO上提到的多项建议,比如使convertView
无效,传递对ListView
的引用并使用invalidateViews()
(导致永久循环,但没有明显的变化,这是有意义的)。
我真的很想要这个,因为我真的不想在数据可用之前加载带有图像占位符的布局。我工作了,但看起来很讨厌,喜欢简单的出路。所以我需要ListView
仅在进度完成时更新(添加项目)。有什么想法吗?
编辑:澄清:数据是在恰当的时间在适配器上设置的。问题是,适配器首先创建一个空白View
(占位符)(不知道任何其他方式,否则你将在getView中得到NullPointerException),然后这个View
被另一个膨胀/替换View
中的onProgressUpdate()
。第二个View
是应该可见的人。这有点工作,因为我可以在新膨胀的视图上获取和设置属性。这些更改是不可见的,我仍然看到空白的,最初创建的视图。我想在每个添加的项目上更新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
而且我知道我可以修改我的AsynTask并且我不需要在很多地方传递对View的引用,但是再次,它正在进行中。简化示例......
修改
好的,所以我的方法似乎开始很差,导致需要AsyncTask
HistoryAdapter
。我解决了一些问题来解决这个问题。
PropertyChangeListener
。这实现了OnSharedPreferenceChangeListener
,以便通知我需要的数据更改。然后将更改传播给任何感兴趣的听众。数据在应用程序启动时解密并存储在全局变量中,该变量可在整个应用程序中访问。将此视为他所提及的“内存缓存”。AsyncTasks
。ListView
中所需的图像存储在SparseArray
中,因此它们只会被创建并存储一次。不要使用HashMap
!此外,只有当前View
的图像不在HashMap
中时才会创建图像(图像不是唯一的)。 public class HistoryAdapter extends BaseAdapter{
private static Context context = ApplicationSubclass.getApplicationContext_();
private Contacts contacts = Contacts.init(context);
private SparseArray<Drawable> c_images = new SparseArray<Drawable>();
private HashMap<Long, Drawable> contact_imgs = new HashMap<Long, Drawable>();
private ArrayList<HistoryHolder> history;
private LayoutInflater mInflater;
public HistoryAdapter(Context context) {
HistoryAdapter.context = context;
mInflater = LayoutInflater.from(context);
}//end constructor
...
public View getView(int position, View convertView, ViewGroup parent) {
final HistoryHolder item = history.get(position);
Drawable d = null;
if (c_images.get(position) == null){
if (!contact_imgs.containsKey(item.CONTACT_ID)){
if (item.IN_CONTACTS && item.CONTACT_ID != 0 && item.CONTACT_ID != -1){
Bitmap photo = contacts.getContactPhotoThumbnailByContactId(item.CONTACT_ID);
if (photo != null){
d = Convert.bitmapToDrawable(context, photo, 128, 128);
} else {
d = context.getResources().getDrawable(R.drawable.ic_contact_picture);
}
} else {
d = context.getResources().getDrawable(R.drawable.ic_contact_picture);
}
contact_imgs.put(item.CONTACT_ID, d);
}
}
c_images.put(position, contact_imgs.get(item.CONTACT_ID));
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.history_list_item_holo_dark, null);
holder = new ViewHolder();
holder.POSITION = position;
holder.TITLE = (TextView) convertView.findViewById(R.id.title);
holder.SUMMARY = (TextView) convertView.findViewById(R.id.summary);
holder.DATE = (TextView) convertView.findViewById(R.id.date);
holder.CONTACT_ICON = (ImageView) convertView.findViewById(R.id.icon);
holder.CONTACT_ICON.setTag(position);
holder.OPTIONS_ICON = (ImageView) convertView.findViewById(R.id.options);
holder.OPTIONS_ICON.setFocusable(false);
holder.OPTIONS_ICON.setTag(position);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.CONTACT_ICON.setBackgroundDrawable(c_images.get(position));
holder.TITLE.setText(item.TITLE);
holder.SUMMARY.setText(item.SUMMARY);
holder.SUMMARY.setMaxLines(2);
holder.DATE.setText(item.DATE);
if (!MathUtils.isEven(position)){
convertView.setBackgroundColor(context.getResources().getColor(R.color.darker));
} else {
convertView.setBackgroundColor(context.getResources().getColor(R.color.medium_dark));
}
return convertView;
}//end method
static class ViewHolder {
TextView TITLE;
TextView SUMMARY;
TextView DATE;
ImageView CONTACT_ICON;
ImageView OPTIONS_ICON;
int POSITION;
}//end inner class
}//end class
答案 0 :(得分:1)
您需要分开您的疑虑。解密/解码数据在UI层中没有位置。
一般情况下,您目前使每个项目使用AsyncTasks的方法很难,并且(有些人会说)由于多种原因而出错:
Honeycomb及以上版本,除非您明确设置执行程序,否则在任何一个时间点都只运行一个AsyncTask。
更重要的是,通过保持对convertView
的引用,您正在从ListView中泄漏抽象 - 您持有引用的视图可能会被重用于其他位置。除非您小心翼翼地取消AsyncTasks并确保正确的结果传递,否则将会给您带来麻烦。
如上所述,解密/解码数据在UI层中没有位置(我考虑了Adapters UI层,因为它们对执行速度有类似的限制)。
如果我是你,我会使用解密数据的内存缓存,随着需求的变化扩展/收缩。然后,我只需要在适配器的getView()
方法中获取解密数据。为了避免在滚动时解密项目,您可以在ListView
上设置滚动侦听器,这样您只能在列表不移动时显示项目。在ApiDemos中有一个类似的演示。
编辑:
至于您明显的问题,您需要重新定义视图(result
)而不将其添加到列表项(任务中的convertView
字段)。您可以通过将其添加到convertView
(例如,在空布局中)来解决此问题。同样,这在所有情况下都不会像您预期的那样有效。
答案 1 :(得分:1)
Dmmh,你说的是什么,你想做什么(所以我需要ListView才能在进度完成时更新(添加项目))你正在做什么(getView中的AsyncTask)是完全相反的事情。 / p>
在getView的末尾使用AsyncTask(但是以不同的方式)用于延迟图像加载,即显示大图像并显示文本+图像占位符,直到下载完成。
您正尝试在First AsyncTask中逐渐填写适配器的数据库,但每次通知观察者数据集中的更改时,您将为数据集中的每个项目提供另一个getView调用循环。不好。
首先,从不,从不!!! 假设getView将为您提供一个转换视图,之前为此位置填充。因此,您必须使用新值重新填充转换视图,或者在每次要求时关闭性能优化并提供新视图。 ListView没有办法关闭回收尝试,因为这是ListView的本质,它是构建它的功能。
第二个(由第一个产生),完全避免将时间昂贵(或用户输入)数据存储到新创建的视图中。视图来来去去,你不想走很长的路来获取昂贵的数据(或者只是丢失用户输入)。唯一的部分排除是大图像延迟加载的简单无效实现。为了减少内存使用量,他们只下载用户当前可见的图像。更有效的实现使用off-ListView缓存。
所以,如果你真的想让你的ListView中的项目一次添加一个,但是要完全荣耀,你应该:
0 *。如果你必须加载用户提供的图标并且这需要花费大量时间来加载(我还没有理解你的初始帖子),请创建一个空的ArrayList来缓存加载的图标并通过索引访问它们。如果某些索引已经提供了所有图像,请忽略此事。
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
}
声明DecryptedRecord类,包含填写视图的必要数据,例如:
在AsyncTask中:加载每个HistoryHolder后,执行“heavy decrypting”并使用值填充新的DecryptedRecord。如果需要加载cusom图像(参见no.0 *),请在此处加载并将其索引存储在缓存中(如果0 *不相关则忽略此项)。使用setTag()将填充的DecryptedRecord附加到HistoryHolder。调用publishProgress(HistoryHolder)
在onProgressUpdate中,只需将HistoryHolder添加到Adapter并调用historyAdapter.notifyDataSetChanged();
在getView()同步中,如果convertView为null,则为视图充气,并且 IN ALL CASES 使用从HistoryHolder.getTag获取的DecryptedRecord中的数据填充它( )。包含在您的ImageViews中调用setImageBitmap()按索引在相应列表中添加必要的位图。
这应该做你想要的。如果您有错误/问题,请尝试在您的问题中包含完整的代码,否则很难理解您的具体情况。希望有所帮助。
答案 2 :(得分:1)
您可以创建一个内容提供程序,它将您的解码数据存储在db / temp数据结构中,并使用它来更新您的视图。解码等可以通过服务/线程在后台进行。适配器可以通过此提供程序与解码数据通信。这与上面提到的alexei burmistrov想法有关。
不是一个好的选择 - 使用当前的布局 - 一个既有空白又有空白的布局文件。填充布局。首次显示布局时,空白视图可见。当任务完成时,可见性设置为消失。
此解决方案不是最佳的,因为每次调用getView时都会运行asynctask。 Android会重新使用视图,因此可能会根据您在UI等方面的滚动方式多次使用。
示例代码:
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
}
}
}
答案 3 :(得分:0)
首先,当异步任务运行onPostExecute时,您必须将数据设置为适配器和notifydatasetchanged()。 使用像创建progressBar之类的东西,当列表视图为空时,现在更清洁一点,并将其设置为listview,如下所示:
listView.setEmptyView(progressbar);