9

好吧,这个话题过去和现在都在争论很多,我已经阅读了很多教程、提示并看到了关于它的讨论。但是,每当我的行达到一定的复杂性时,我仍然在为 ListView 实现自定义 BaseAdapter 时遇到问题。所以我基本上拥有的是通过解析来自网络的 xml 获得的一些实体。此外,我还获取了一些图像等,所有这些都是在 AsyncTask 中完成的。我在 getView() 方法中使用性能优化 ViewHandler 方法,并按照每个人的建议重用 convertView。即我希望我使用 ListView 应该是这样,当我只显示一个 ImageView 和两个 TextViews 时,它真的很好用,它们的样式是 SpannableStringBuilder (我不使用任何 HTML.fromHTML ) .

现在它来了。每当我使用多个小的 ImageView、一个 Button 和更多的 TextView 扩展我的行布局时,所有这些都使用 SpannableStringBuilder 进行了不同的样式设置,我得到了停止的滚动性能。该行由作为父级的 RelativeLayout 组成,所有其他元素都使用布局参数进行排列,因此我无法让该行的布局更简单。我必须承认,我从未见过任何包含这么多 UI 元素的行的 ListView 实现示例。

但是,当我在 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;
}

为了清楚起见,我将它用于两个适配器。

4

2 回答 2

7

您应该使用 DDMS 的分析器来准确查看时间花费的位置。我怀疑您在 getView() 中所做的事情很昂贵。例如,是否 viewUtility.setUserIcon(holder.usericon, blogs.get(position).getUid(), 30); 每次都创建一个新图标?一直解码图像会产生打嗝。

于 2011-08-30T18:40:24.170 回答
2

读了很多;)

我看不出你的布局有什么问题。你可以优化 - 你的第一个 if 使用 || - 在变量中缓存 blogs.get( position ) - 声明你是常量静态的。- 为什么你使用日历并将其装饰回 ms ?你好像已经有了你的女士?

但我担心这还不够。

问候, 斯蒂芬

于 2011-08-30T23:10:23.270 回答