android中的ViewHolder模式有什么好处?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21501316/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 04:40:51  来源:igfitidea点击:

What is the benefit of ViewHolder pattern in android?

androidlistviewandroid-arrayadapterandroid-viewholder

提问by mohammad jannesary

When you are developing an Android program; and you want to have a ArrayAdapteryou can Simply have a Class (most of times with ViewHoldersuffix) or directly inflate your convertViewand find your view by id.
So What is the benefit of using ViewHolder?
The example of both here :

当你在开发安卓程序时;并且您想要一个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");
}

Or create an inner class in the ArrayAdapter as following:

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

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

and finally in the getView :

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

采纳答案by Raghunandan

Understand how listview recycling works

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

How ListView's recycling mechanism works

ListView 的回收机制是如何工作的

You cannot recycle a row that is presently in use. The above link explains how listview recycling mechanism works

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

So What is the benefit of using ViewHolder?

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

Quoting docs

引用文档

Your code might call findViewById()frequently during the scrolling of ListView, which can slow down performance. Even when the Adapter returns an inflated view for recycling, you still need to look up the elements and update them. A way around repeated use of findViewById()is to use the "view holder" design pattern.

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

You missed the important part convertView.setTag(holder)and holder = (ViewHolder) ConvertView.getTag()

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

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

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

回答by ataulm

As you fling through your ListView, there's only a handful of views being shown at any given time. This means that you don't have to instantiate a view for every item in your adapter; when a view scrolls off-screen, it can be reused, or recycled.

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

View recycling and the ViewHolder pattern are not the same. The ViewHolder pattern is solely to reduce the number of view.findViewById(int)calls you make. The ViewHolder pattern only works when you take advantage of view recycling.

视图回收和 ViewHolder 模式不一样。ViewHolder 模式只是为了减少view.findViewById(int)您拨打的电话次数。ViewHolder 模式仅在您利用视图回收时有效。

In getView(int position, View convertView, ViewGroup parent), the convertViewparameter is eithernull or it's a view that has been recycled: it will still have the data from a different list item bound to it.

在 中getView(int position, View convertView, ViewGroup parent)convertView参数要么为空,要么是一个已被回收的视图:它仍会将来自不同列表项的数据绑定到它。

Without the ViewHolder pattern, you can still take advantage of view recycling (i.e. not blindly instantiating views):

没有 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;
}

Above is an example of view recycling - we do not inflate a new View for each row; we only inflate a view if we're not given one to reuse. Avoiding having to inflate a view is the part that will definitely help with performance when scrolling through your list: take advantage of view recycling.

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

So, what's the ViewHolder for then? We're currently doing 4x findViewById(int)for everyitem, regardless of whether the row itself already existed. As findViewById(int)recursively iterates down a ViewGroup til it finds a descendent with the given ID, this is a bit pointless for our recycled views - we're re-finding views that we already have references to.

那么,ViewHolder 有什么用呢?我们目前findViewById(int)每个项目都执行 4 倍,无论该行本身是否已经存在。由于findViewById(int)递归地向下迭代 ViewGroup 直到它找到具有给定 ID 的后代,这对于我们回收的视图来说有点毫无意义——我们正在重新查找我们已经引用过的视图。

Avoid this by using a ViewHolder object to hold references to the sub-views after you "find" them:

在“找到”子视图后,通过使用 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)allows you to tell the View to hold an arbitrary object. If we use it to hold an instance of our ViewHolder after we do our findViewById(int)calls, then we can use View.getTag()on recycled views to avoid having to make the calls again and again.

View.setTag(Object)允许您告诉 View 持有任意对象。如果我们在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, but that's the benefit of the ViewHolder.

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

回答by marcinj

ViewHolderdesign pattern is used to speed up rendering of your ListView- actually to make it work smoothly, findViewById is quite expensive (it does DOM parsing) when used each time a list item is rendered, it must traverse your layout hierarchy and also instantiate objects. Since lists can redraw its items quite frequently during scrolling such overhead might be substantial.

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

you can find good explanation of how this works in :

你可以找到关于它是如何工作的很好的解释:

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

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

starting from minute 10, you have explained ViewHolder design pattern by google experts.

从第 10 分钟开始,您已经解释了 google 专家的 ViewHolder 设计模式。

[edit]

[编辑]

findViewById is not instantiating new objects, it only traverses hierarchy - here is reference http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/java/android/view/ViewGroup.java#3610

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

回答by mohammed momn

Firstly :

首先 :

In ListViewwhen you scroll the ListViewyou need to create new item and bind its data on it so if you have much items in ListViewit may cause memory leak because more objects you created for items , but Android using concept of recycle most of its API , and it meaning you create one object and use it instead of destroy it and declare new one , so when you scroll ListViewAPI using the invisible items you scrolled and pass it for you in getViewmethod it is convertViewso here you deal with more items ListView

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

Secondly :

其次 :

if you have custom item in ListViewyou need to attach your custom Layout on each item of the ListViewso you will each time ListViewbind new item using findViewByIdto get reference of the layout items. This method will go to search for your item in recursive way so you ViewHolderwill help you to make the recursive done for one time only and then it will hold the reference of the layout item for you till you can attach it for ListView

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

hope this help you and feed me back in any not obvious thing

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