【问题标题】:What is the benefit of ViewHolder pattern in android?android中ViewHolder模式有什么好处?
【发布时间】:2014-02-25 09:38:39
【问题描述】:

当您开发 Android 程序时;并且你想拥有一个 ArrayAdapter 你可以简单地拥有一个类(大多数时候带有 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");
}

或者在 ArrayAdapter 中创建一个内部类,如下所示:

static class ViewHolder {   
    ImageView leftIcon;   
    TextView upperLabel;  
    TextView lowerLabel;  
}

最后在 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);
    }
}

【问题讨论】:

    标签: android listview android-arrayadapter android-viewholder


    【解决方案1】:

    了解列表视图回收的工作原理

    How ListView's recycling mechanism works

    您无法回收当前正在使用的行。上面的链接解释了listview回收机制是如何工作的

    那么使用 ViewHolder 有什么好处呢?

    引用文档

    您的代码可能会在 ListView 滚动期间频繁调用 findViewById(),这会降低性能。即使适配器返回一个膨胀的视图进行回收,您仍然需要查找元素并更新它们。避免重复使用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; 
        }
    

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

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

    【讨论】:

    • 感谢您提供有用的信息。那么它对 notifyDataSetChanged 有影响吗?
    • 当您想通过添加新项目或从中删除来刷新列表视图时,您将使用notifyDataSetChanged。是的,您的 getView 被调用。但是使用 ViewHolder 可以提高性能。这与notfiyDatSetChanged() 无关
    • @Reghunandan 我更改了程序并使用了 ViewHolder;我有一个 400 行的列表视图;滚动速度增加了很多;谢谢
    • @mohammadjannesary 观众肯定有帮助。
    【解决方案2】:

    当您浏览您的 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;
    }
    

    上面是一个视图回收的例子——我们不会为每一行膨胀一个新的视图;如果我们没有得到一个可以重用的视图,我们只会夸大一个视图。 避免让视图膨胀是在滚动列表时绝对有助于提高性能的部分:利用视图回收。

    那么,ViewHolder 有什么用呢?我们目前正在为 每个 项执行 4x findViewById(int),无论该行本身是否已经存在。当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;
      }
    }
    

    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);
    }
    

    The performance benefits of this optimisation is questionable,但那是benefit of the ViewHolder

    【讨论】:

      【解决方案3】:

      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

      【讨论】:

        【解决方案4】:

        首先:

        ListView 中,当您滚动ListView 时,您需要创建新项目并将其数据绑定在其上,因此如果ListView 中有很多项目,则可能会导致内存泄漏,因为您为项目创建了更多对象,但是Android 使用回收其大部分 API 的概念,这意味着您创建一个对象并使用它而不是销毁它并声明新对象,因此当您滚动 ListView API 时使用您滚动的不可见项目并将其传递给 @ 987654325@ 方法是convertView 所以在这里你处理更多的项目ListView

        其次:

        如果您在ListView 中有自定义项,则需要将自定义布局附加到ListView 的每个项上,这样您每次ListView 使用findViewById 绑定新项以获取布局项的引用。此方法将以递归方式搜索您的项目,因此您 ViewHolder 将帮助您仅完成一次递归,然后它将为您保留布局项目的引用,直到您可以将其附加到 @987654333 @

        希望这对您有所帮助,并在任何不明显的事情上给我反馈

        【讨论】:

        • 不利用视图回收机制并不是内存泄漏的原因。内存泄漏!= 高内存消耗
        猜你喜欢
        • 1970-01-01
        • 2018-01-11
        • 1970-01-01
        • 2019-08-25
        • 1970-01-01
        • 2023-04-05
        • 2012-03-15
        • 2013-12-31
        相关资源
        最近更新 更多