在ListView中实现ViewHolder模式的优化

5
所以通常使用的ViewHolder模式如下(ListAdapter):
    ...

    @Override
    public View getView(final int position, View convertView, final ViewGroup parent) {

        final Album album = albums.get(position);

        ViewHolder viewHolder = null;
        if (convertView==null){
            convertView = inflater.inflate(R.layout.albums_list_item, null);

            final ImageView albumImage = (ImageView) convertView.findViewById(R.id.album_icon);

            final TextView txtTitle = (TextView) convertView.findViewById(R.id.album_title);

            final TextView txtDescription = (TextView) convertView.findViewById(R.id.album_copyright);

            viewHolder = new ViewHolder();
            viewHolder.albumImage = albumImage;
            viewHolder.txtTitle = txtTitle;
            viewHolder.txtDescription = txtDescription;
            convertView.setTag(viewHolder);
        }
        else
            viewHolder = (ViewHolder)convertView.getTag();

        viewHolder.txtTitle.setText(album.getTitle(locale));
        viewHolder.txtDescription.setText(album.getCopyrightInfo(locale));
        ...
        return convertView;
    }

ViewHolder类通常看起来像这样:

static class ViewHolder{
    public ImageView previewImage;
    public TextView txtTitle;
    public TextView txtDescription;
}

我的问题与ViewHolder实现有关。
1)为什么不使用构造函数而是初始化每个字段?
2)为什么要使用默认访问类型而不是受保护的(实际上应该是私有的,但这会影响由JIT创建的静态访问器的性能)?好吧,我猜只涉及继承。
那么为什么以下模式不更好(不包括“受保护 vs 默认”访问类型):

protected static class ViewHolder{
    public final ImageView previewImage;
    public final TextView txtTitle;
    public final TextView txtDescription;

    public ViewHolder (final ImageView previewImage,  final TextView txtTitle, final TextView txtDescription){
        this.previewImage = previewImage;
        this.txtTitle = txtTitle;
        this.txtDescription = txtDescription;
    }
}

ListAdapter仅有的更改是:

...
final TextView txtDescription = (TextView) convertView.findViewById(R.id.album_copyright);
viewHolder = new ViewHolder(albumImage, txtTitle, txtDescription);
convertView.setTag(viewHolder);
...

无论如何,它必须调用构造函数。这只是品味的问题吗?还是这个版本在某种程度上更慢,或者以某种方式影响性能?
3个回答

5

我使用的方法与你的非常相似,但我更进一步,因为ViewHolder是适配器类的私有部分,我通过将视图传递到它的构造函数中并在那里设置值来将其与类紧密耦合。

    private class ViewHolder
    {
      protected final ImageView image;
      protected final TextView  title;
      protected final TextView  status;

      public ViewHolder( final View root )
      {
         image = (ImageView) root.findViewById( R.id.artist_image );
         title = (TextView) root.findViewById( R.id.artist_title );
         status = (TextView) root.findViewById( R.id.artist_status );
      }
   }

getView(...)方法中

 View row = convertView;

  if ( null == row || null == row.getTag() )
  {
     row = inflater.inflate( R.layout.adapter_artists, null );
     holder = new ViewHolder( row );
     row.setTag( holder );
  }
  else
  {
     holder = (ViewHolder) row.getTag();
  }

我喜欢这样做是因为它使得我在getView(...)中的适配器代码更简单且具有final变量的好处。如果我将其改为protected,可能会获得轻微的速度提升,但即使是处理大型列表,我发现其性能也足够。


3
安卓开发者(例如Romain Guy)表示,ViewHolder必须是静态类(出于性能考虑),而且如果其外部类用于适配器时使用私有访问类型是个坏主意(由于它必须是静态的,所以不能成为适配器的内部类),因此它必须比私有范围更广(他们使用上述提到的默认访问类型)。 - Stan
是的,我记得读过关于这方面的内容,但我还没有进行过性能方面的良好实验。我想展示的是构造函数模式,然后你就会遇到确保其他类在静态状态下无法访问它的问题。使用static protected是否足够限制呢? - ScouseChris
最接近私有访问类型的是受保护的,所以没错。我喜欢你的方法,现在正在更改我的代码来使用它。实际上并没有什么区别,只是关于代码的外观而已。因为只需要发送一个参数而不是一堆参数。虽然这违反了耦合和内聚性,但ViewHolder就像适配器的卫星类,所以没什么大不了的。 - Stan
是的,我知道紧密耦合不好,但我解释说没有其他东西可以使用那个viewholder类,这使得代码看起来更加漂亮! - ScouseChris

2

我认为这只是品味问题。对我来说,它甚至比标准版看起来更好。此外,您的版本可能会因使用final变量而潜在地更快。


谢谢,这正是我所思考的方式。 - Stan

1

在我看来,这是最好的方法,但你的代码中有一些东西需要改变。在你的ViewHolder中有一个构造函数,你在其中设置了视图,但我注意到你没有在代码中使用它。我建议你使用它或者将其删除。另外,实际上还有一种更好的方式可以获得相同的效果,但只能在Android 4+上运行:

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    ImageView mIcon;
    TextView mName;
    if (convertView == null) {
        convertView = LayoutInflater.from(context)
          .inflate(R.layout.my_contact_listing, parent, false);
        mIcon = (ImageView) convertView.findViewById(R.id.contact_icon);
        mName = (TextView) convertView.findViewById(R.id.contact_name);
        convertView.setTag(R.id.contact_icon, mIcon);
        convertView.setTag(R.id.contact_name, mName);
    } else {
        mIcon = (ImageView) convertView.getTag(R.id.contact_icon);
        mName = (TextView) convertView.getTag(R.id.contact_name);
    }

    Contact mContact = getItem(position);
    mName.setText(mContact.getName());
    mIcon.setImageResource(mContact.getIcon());

    return convertView;
}

谢谢。但是它不使用ViewHolder视图吗?(“但是我看你的代码中没有使用它”): viewHolder.txtTitle.setText(album.getTitle(locale)); viewHolder.txtDescription.setText(album.getCopyrightInfo(locale)); - Stan
确切地说,您可以使用视图的setTag方法来实现这一点:http://developer.android.com/reference/android/view/View.html#setTag(int, java.lang.Object),供参考。 - hardartcore
你的意思是代码行:convertView.setTag(viewHolder); 吗?我没有显示这行,因为与原始代码相比没有任何更改。ListAdapter中唯一的更改是ViewHolder的创建以及其字段的初始化。 - Stan
2
我在谈论 convertView.setTag(R.id.contact_name, mIcon);,在这里你可以阅读更多关于适配器良好实践的信息:http://www.piwai.info/android-adapter-good-practices/ - hardartcore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接