このトピックは以前も今も非常に多く議論されており、私はすでに多くのチュートリアル、ヒントを読み、それについての話を見ました。しかし、行が特定の複雑さに達すると、ListView のカスタム BaseAdapter の実装にまだ問題があります。したがって、私が基本的に持っているのは、ネットワークからの xml を解析することによって得られるいくつかのエンティティです。さらに、いくつかの画像などを取得します。これはすべて AsyncTask で行われます。getView() メソッド内でパフォーマンスを最適化する ViewHandler アプローチを使用し、誰もが提案するように convertView を再利用します。つまり、ListView を想定どおりに使用していることを願っており、SpannableStringBuilder でスタイル設定された単一の ImageView と 2 つの TextView を表示しているだけで、実際に正常に動作します (HTML.fromHTML は一切使用しません)。 .
そして今ここに来ます。複数の小さな ImageView、Button、および SpannableStringBuilder ですべて異なるスタイルの TextView を使用して行レイアウトを拡張すると、スクロール パフォーマンスが停止します。行は親として RelativeLayout で構成され、他のすべての要素はレイアウト パラメーターで配置されているため、行のレイアウトをよりシンプルにすることはできません。これほど多くの UI 要素を行に含む ListView 実装の例を見たことがないことを認めなければなりません。
ただし、ScrollView 内で TableLayout を使用し、AsyncTask ( onProgressUpdate() によって新しい行が着実に追加されます) で手動で埋めると、何百もの行があっても完全にスムーズに動作します。リストの最後までスクロールすると、新しい行が追加されると少しつまずきます。それ以外の場合は、スクロール時に常につまずく 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>
** * ** * ** * *更新* ** * ** * ** * ** *
結局のところ、問題は BaseAdapter の代わりに ArrayAdapter を使用することで簡単に解決されました。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;
}
}
+++++++++++++++++ トレースを含む 2 番目の編集 +++++++++++++++++++++ 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
ご覧のとおり、これら 2 つのアダプターには大きな違いがあります (getView() メソッドでは ArrayAdapter の方が 4 倍高速です)。そして、なぜこれがそれほど劇的なのか、私にはまったくわかりません。ArrayAdapter には何らかの優れたキャッシングまたはさらなる最適化があるとしか思えません。
++++++++++++++++++++++++++もうひとつの更新++++++++++++++++++ 私の現在のUIBlogクラスがどのように構築されているかを示すには:
private class UIBlog {
Blog blog;
CharSequence seperatorTxt;
Bitmap icon;
CharSequence titleTxt;
CharSequence dateTxt;
CharSequence amountTxt;
CharSequence authorTxt;
}
明確にするために、これを両方のアダプターに使用しています。