ViewHolder有什么好处?

moh*_*ary 39 android listview android-arrayadapter android-viewholder

在开发Android程序时; 并且你想要一个ArrayAdapter你可以简单地拥有一个Class(大多数时候使用ViewHolder后缀)或直接膨胀你的convertView并通过id找到你的视图.
那么使用ViewHolder有什么好处?
两个例子都在这里:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = ((Activity)getContext()).getLayoutInflater().inflate(R.layout.row_phrase, null);
    }
    ((TextView) convertView.findViewById(R.id.txtPhrase)).setText("Phrase 01");
}
Run Code Online (Sandbox Code Playgroud)

要么 :

static class ViewHolder {   
    ImageView leftIcon;   
    TextView upperLabel;  
    TextView lowerLabel;  
}
Run Code Online (Sandbox Code Playgroud)

最后在getView中:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder = null;
    if (view == null) {
        view = LayoutInflater.from(context).inflate(R.layout.row_layout,
                    null, false);
        holder = new ViewHolder();
        holder.leftIcon = (ImageView) view.findViewById(R.id.leftIcon);
    }
}
Run Code Online (Sandbox Code Playgroud)

Rag*_*dan 36

了解listview回收的工作原理

ListView的回收机制如何运作

您无法回收当前正在使用的行.以上链接解释了listview回收机制的工作原理

那么使用ViewHolder有什么好处?

引用文档

findViewById()在滚动ListView期间,您的代码可能会频繁调用,这会降低性能.即使适配器返回一个膨胀的视图以进行回收,您仍然需要查找元素并更新它们.绕过重复使用的方法findViewById()是使用"视图持有者"设计模式.

    public View getView(int position, View convertView, ViewGroup parent) { 
             ViewHolder holder; 

             if (convertView == null) { // if convertView is null
                 convertView = mInflater.inflate(R.layout.mylayout, 
                         parent, false);
                 holder = new ViewHolder(); 
                     // initialize views  
                convertView.setTag(holder);  // set tag on view
            } else { 
                holder = (ViewHolder) convertView.getTag();
                        // if not null get tag 
                        // no need to initialize
            } 

            //update views here  
            return convertView; 
    }
Run Code Online (Sandbox Code Playgroud)

你错过了重要的部分convertView.setTag(holder)holder = (ViewHolder) ConvertView.getTag()

http://developer.android.com/training/improving-layouts/smooth-scrolling.html

  • 如果要通过添加新项目或从中删除列表视图来刷新列表视图,则将使用`notifyDataSetChanged`.是的,你的getView被调用.但使用ViewHolder可以提高性能.这与`notfiyDatSetChanged()`无关 (3认同)
  • @Reghunandan我更改了程序并使用了ViewHolder;我有一个包含400行的列表视图;滚动速度大大提高;谢谢 (2认同)

ata*_*ulm 18

当您浏览ListView时,在任何给定时间都只显示少量视图.这意味着您不必为适配器中的每个项实例化视图; 当视图在屏幕外滚动时,可以重复使用或回收.

查看回收和ViewHolder模式不一样.ViewHolder模式仅用于减少view.findViewById(int)您拨打的电话数量.ViewHolder模式仅在您利用视图回收时才起作用.

getView(int position, View convertView, ViewGroup parent)中,convertView参数是要么空,或者它已经回收一个观点:它仍然会从绑定到一个不同的列表项拥有的数据.

如果没有ViewHolder模式,您仍然可以利用视图回收(即不盲目实例化视图):

public View getView(int position, View convertView, ViewGroup parent) {
  View view = convertView;
  if (view == null) {
    view = // inflate new view
  }

  ImageView imageView = (ImageView) view.findViewById(R.id.listitem_image);
  TextView textView = (TextView) view.findViewById(R.id.listitem_text);
  TextView timestampView = (TextView) view.findViewById(R.id.listitem_timestamp);
  ProgressBar progressSpinnerView = (ProgressBar) view.findViewById(R.id.progress_spinner);

  // TODO: set correct data for this list item
  // imageView.setImageDrawable(...)
  // textView.setText(...)
  // timestampView.setText(...)
  // progressSpinnerView.setProgress(...)

  return view;
}
Run Code Online (Sandbox Code Playgroud)

以上是视图回收的示例 - 我们不会为每行充气新视图; 如果我们没有给予重用,我们只会给视图充气.在滚动列表时,避免必须给视图充气是肯定有助于提高性能的部分:利用视图回收.

那么ViewHolder当时是什么?我们目前正在findViewById(int)每个项目做4次,无论行本身是否已经存在.当findViewById(int)递归迭代ViewGroup直到找到具有给定ID的后代时,这对于我们的回收视图来说有点无意义 - 我们正在重新查找我们已经引用的视图.

通过使用ViewHolder对象在"找到"它们之后保存对子视图的引用来避免这种情况:

private static class ViewHolder {
  final TextView text;
  final TextView timestamp;
  final ImageView icon;
  final ProgressBar progress;

  ViewHolder(TextView text, TextView timestamp, ImageView icon, ProgressBar progress) {
    this.text = text;
    this.timestamp = timestamp;
    this.icon = icon;
    this.progress = progress;
  }
}
Run Code Online (Sandbox Code Playgroud)

View.setTag(Object)允许您告诉视图保存任意对象.如果我们在执行findViewById(int)调用后使用它来保存ViewHolder的实例,那么我们可以使用View.getTag()回收的视图来避免不得不一次又一次地进行调用.

public View getView(int position, View convertView, ViewGroup parent) {
  View view = convertView;
  if (view == null) {
    view = // inflate new view
    ViewHolder holder = createViewHolderFrom(view);
    view.setTag(holder);  
  }
  ViewHolder holder = view.getTag();
  // TODO: set correct data for this list item
  // holder.icon.setImageDrawable(...)
  // holder.text.setText(...)
  // holder.timestamp.setText(...)
  // holder.progress.setProgress(...)
  return view;
}

private ViewHolder createViewHolderFrom(View view) {
    ImageView icon = (ImageView) view.findViewById(R.id.listitem_image);
    TextView text = (TextView) view.findViewById(R.id.listitem_text);
    TextView timestamp = (TextView) view.findViewById(R.id.listitem_timestamp);
    ProgressBar progress = (ProgressBar) view.findViewById(R.id.progress_spinner);

    return new ViewHolder(text, timestamp, icon, progress);
}
Run Code Online (Sandbox Code Playgroud)

这种优化的性能优势值得怀疑,但这是ViewHolder优势所在.


mar*_*inj 14

ViewHolder设计模式用于加速你的渲染ListView- 实际上是为了使它顺利运行,findViewById非常昂贵(它进行DOM解析),每次渲染列表项时都使用它,它必须遍历你的布局层次结构并实例化对象.由于列表可以在滚动期间非常频繁地重绘其项目,因此这种开销可能很大.

你可以找到它的工作原理的很好的解释:

http://www.youtube.com/watch?v=wDBM6wVEO70&feature=youtu.be&t=7m

从第10分钟开始,您已经通过谷歌专家解释了ViewHolder的设计模式.

[编辑]

findViewById没有实例化新对象,它只遍历层次结构 - 这里是参考http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/java/android/view/ViewGroup.java#3610