【发布时间】:2011-11-07 00:27:23
【问题描述】:
嗯,这个话题曾经而且现在仍然有很多争论,我已经阅读了很多教程、提示并看到了关于它的讨论。但是,每当我的行达到一定的复杂性时,我仍然在为 ListView 实现自定义 BaseAdapter 时遇到问题。 所以我基本上拥有的是通过解析来自网络的 xml 获得的一些实体。此外,我还获取了一些图像等,所有这些都是在 AsyncTask 中完成的。 我在 getView() 方法中使用性能优化 ViewHandler 方法,并按照每个人的建议重用 convertView。 IE。我希望我使用 ListView 应该是这样,当我只显示一个 ImageView 和两个 TextView 时,它真的很好用,它们的样式是 SpannableStringBuilder (我不使用任何 HTML.fromHTML )。
现在它来了。每当我使用多个小的 ImageView、一个 Button 和更多的 TextView 扩展我的行布局时,所有这些都使用 SpannableStringBuilder 进行了不同的样式设置,我得到了停止的滚动性能。该行由作为父级的 RelativeLayout 组成,所有其他元素都使用布局参数进行排列,因此我无法让该行的布局更简单。我必须承认,我从未见过任何 ListView 实现的示例,其中行包含这么多 UI 元素。
但是,当我在 ScrollView 中使用 TableLayout 并使用 AsyncTask 手动填充它(由 onProgressUpdate() 稳定添加的新行)时,即使其中有数百行,它的行为也非常流畅。如果滚动到列表的末尾,当添加新行时,它只会有点绊脚石。否则它会比使用 ListView 更流畅,因为 ListView 滚动时总是绊倒。
当 ListView 不想表现良好时,有什么建议吗?我应该继续使用 TableLayout 方法还是建议使用 ListView 来优化性能?
这是我的适配器的实现:
protected class BlogsSeparatorAdapter extends BaseAdapter {
private LayoutInflater inflater;
private final int SEPERATOR = 0;
private final int BLOGELEMENT = 1;
public BlogsSeparatorAdapter(Context context) {
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return blogs.size();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
int type = BLOGELEMENT;
if (position == 0) {
type = SEPERATOR;
} else if (isSeparator(position)) {
type = SEPERATOR;
}
return type;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
UIBlog blog = getItem(position);
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.blogs_row_layout, null);
holder.usericon = (ImageView) convertView.findViewById(R.id.blogs_row_user_icon);
holder.title = (TextView) convertView.findViewById(R.id.blogs_row_title);
holder.date = (TextView) convertView.findViewById(R.id.blogs_row_date);
holder.amount = (TextView) convertView.findViewById(R.id.blogs_row_cmmts_amount);
holder.author = (TextView) convertView.findViewById(R.id.blogs_row_author);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.usericon.setImageBitmap(blog.icon);
holder.title.setText(blog.titleTxt);
holder.date.setText(blog.dateTxt);
holder.amount.setText(blog.amountTxt);
holder.author.setText(blog.authorTxt);
return convertView;
}
class ViewHolder {
TextView separator;
ImageView usericon;
TextView title;
TextView date;
TextView amount;
TextView author;
}
/**
* Check if the blog on the given position must be separated from the last blogs.
*
* @param position
* @return
*/
private boolean isSeparator(int position) {
boolean separator = false;
// check if the last blog was created on the same date as the current blog
if (DateUtility.getDay(
DateUtility.createCalendarFromUnixtime(blogs.get(position - 1).getUnixtime() * 1000L), 0)
.getTimeInMillis() > blogs.get(position).getUnixtime() * 1000L) {
// current blog was not created on the same date as the last blog --> separator necessary
separator = true;
}
return separator;
}
}
这是该行的xml(没有按钮,仍然磕磕绊绊):
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:background="@drawable/listview_selector">
<ImageView
android:id="@+id/blogs_row_user_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:paddingTop="@dimen/blogs_row_icon_padding_top"
android:paddingLeft="@dimen/blogs_row_icon_padding_left"/>
<TextView
android:id="@+id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_user_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/blogs_row_title_padding"
android:textColor="@color/blogs_table_text_title"/>
<TextView
android:id="@+id/blogs_row_date"
android:layout_below="@id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_user_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/blogs_row_date_padding_left"
android:textColor="@color/blogs_table_text_date"/>
<ImageView
android:id="@+id/blogs_row_cmmts_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_date"
android:layout_margin="@dimen/blogs_row_cmmts_icon_margin"
android:src="@drawable/comments"/>
<TextView
android:id="@+id/blogs_row_cmmts_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_cmmts_icon"
android:layout_margin="@dimen/blogs_row_author_margin"/>
<TextView
android:id="@+id/blogs_row_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_cmmts_amount"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:ellipsize="marquee"
android:layout_margin="@dimen/blogs_row_author_margin"/>
</RelativeLayout>
********** 更新 *************
事实证明,使用 ArrayAdapter 而不是 BaseAdapter 可以轻松解决问题。我对 ArrayAdapter 使用了完全相同的代码,性能差异是巨大的!它运行起来就像使用 TableLayout 一样流畅。
所以每当我使用 ListView 时,我肯定会避免使用 BaseAdapter,因为它明显较慢且针对复杂布局的优化程度较低。这是一个相当有趣的结论,因为我在示例和教程中没有读过任何关于它的词。或许我没有准确地阅读它。 ;-)
不过,这是运行顺利的代码(您可以看到我的解决方案是使用分隔符对列表进行分组):
protected class BlogsSeparatorAdapter extends ArrayAdapter<UIBlog> {
private LayoutInflater inflater;
private final int SEPERATOR = 0;
private final int BLOGELEMENT = 1;
public BlogsSeparatorAdapter(Context context, List<UIBlog> rows) {
super(context, R.layout.blogs_row_layout, rows);
inflater = LayoutInflater.from(context);
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
int type = BLOGELEMENT;
if (position == 0) {
type = SEPERATOR;
} else if (isSeparator(position)) {
type = SEPERATOR;
}
return type;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final UIBlog blog = uiblogs.get(position);
int type = getItemViewType(position);
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
if (type == SEPERATOR) {
convertView = inflater.inflate(R.layout.blogs_row_day_separator_item_layout, null);
View separator = convertView.findViewById(R.id.blogs_separator);
separator.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// do nothing
}
});
holder.separator = (TextView) separator.findViewById(R.id.blogs_row_day_separator_text);
} else {
convertView = inflater.inflate(R.layout.blogs_row_layout, null);
}
holder.usericon = (ImageView) convertView.findViewById(R.id.blogs_row_user_icon);
holder.title = (TextView) convertView.findViewById(R.id.blogs_row_title);
holder.date = (TextView) convertView.findViewById(R.id.blogs_row_date);
holder.amount = (TextView) convertView.findViewById(R.id.blogs_row_author);
holder.author = (TextView) convertView.findViewById(R.id.blogs_row_author);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (holder.separator != null) {
holder.separator
.setText(DateUtility.createDate(blog.blog.getUnixtime() * 1000L, "EEEE, dd. MMMMM yyyy"));
}
holder.usericon.setImageBitmap(blog.icon);
holder.title.setText(createTitle(blog.blog.getTitle()));
holder.date.setText(DateUtility.createDate(blog.blog.getUnixtime() * 1000L, "'um' HH:mm'Uhr'"));
holder.amount.setText(createCommentsAmount(blog.blog.getComments()));
holder.author.setText(createAuthor(blog.blog.getAuthor()));
return convertView;
}
class ViewHolder {
TextView separator;
ImageView usericon;
TextView title;
TextView date;
TextView amount;
TextView author;
}
/**
* Check if the blog on the given position must be separated from the last blogs.
*
* @param position
* @return
*/
private boolean isSeparator(int position) {
boolean separator = false;
// check if the last blog was created on the same date as the current blog
if (DateUtility.getDay(
DateUtility.createCalendarFromUnixtime(blogs.get(position - 1).getUnixtime() * 1000L), 0)
.getTimeInMillis() > blogs.get(position).getUnixtime() * 1000L) {
// current blog was not created on the same date as the last blog --> separator necessary
separator = true;
}
return separator;
}
}
+++++++++++++++++++ 带痕迹的第二次编辑 +++++++++++++++++++++++强> 只是为了表明 BaseAdapter 的作用与 ArrayAdapter 不同。这只是来自 getView() 方法的整个跟踪,两个适配器中的代码完全相同。
首先是调用量http://img845.imageshack.us/img845/5463/tracearrayadaptercalls.png
http://img847.imageshack.us/img847/7955/tracebaseadaptercalls.png
独占时间消耗 http://img823.imageshack.us/img823/6541/tracearrayadapterexclus.png
http://img695.imageshack.us/img695/3613/tracebaseadapterexclusi.png
包含时间消耗 http://img13.imageshack.us/img13/4403/tracearrayadapterinclus.png
http://img831.imageshack.us/img831/1383/tracebaseadapterinclusi.png
如您所见,这两个适配器之间存在巨大差异(ArrayAdapter 在 getView() 方法中快四倍)。我真的不知道为什么会如此戏剧化。我只能假设 ArrayAdapter 有某种更好的缓存或进一步的优化。
++++++++++++++++++++++++++++只是另一个更新+++++++++++++++++ 向您展示我当前的 UIBlog 类是如何构建的:
private class UIBlog {
Blog blog;
CharSequence seperatorTxt;
Bitmap icon;
CharSequence titleTxt;
CharSequence dateTxt;
CharSequence amountTxt;
CharSequence authorTxt;
}
为了清楚起见,我将它用于两个适配器。
【问题讨论】:
-
你对表格有什么问题?您找到了一个表现良好的解决方案,但为什么更喜欢列表?
-
我当然对表格没有任何问题,这真的只是一个问题“使用 ListView 有任何限制吗?或者你可以总是使用 ListView(具有所有优点)作为列表?”在优化其性能时,也许我以某种方式错误地使用了 ListView。 :-)
-
@user535762:使用 Traceview 找出您的时间都花在了哪里。
-
您的两种方法之间的一个重要区别是您在 ArrayAdapter 版本中只调用一次 get(position)。如果您的 List
随机访问(LinkedList)的成本很高,那可能是一个重要的区别。此外,发布 traceview 的屏幕截图对我们来说也不是很有用。您的屏幕截图未显示 getView() 中发生的情况,因此难以验证您的声明。 -
这晚了,但基于BaseAdapter的原始适配器显然是错误的。 getItem(position) 返回位置?它与 ArrayAdapter 一起工作得更好的原因可能是因为他正在将他的对象从他正在使用的任何东西(可能每次都解析它们)转换为适当的支持数组。
标签: android performance android-listview tablelayout baseadapter