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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11945563/
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 09:15:44  来源:igfitidea点击:

How ListView's recycling mechanism works

androidandroid-listviewandroid-gridview

提问by Andy

So I have this problem I had before, and naturally I asked for help on here. Luksprog's answer was great because I had no idea about how ListView and GridView optimized itself with recycling Views. So with his advice I was able to change how I added Views to my GridView. Problem is now I have something that does not make sense. This is my getViewfrom my BaseAdapter:

所以我以前遇到过这个问题,所以我很自然地在这里寻求帮助。Luksprog 的回答很棒,因为我不知道 ListView 和 GridView 如何通过回收视图优化自身。因此,根据他的建议,我能够更改将视图添加到 GridView 的方式。问题是现在我有一些没有意义的东西。这是我的getView来自我的BaseAdapter



public View getView(int position, View convertView, ViewGroup parent) {
        if(convertView == null) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            convertView = inflater.inflate(R.layout.day_view_item, parent, false);
        }
        Log.d("DayViewActivity", "Position is: "+position);
        ((TextView)convertView.findViewById(R.id.day_hour_side)).setText(array[position]);
        LinearLayout layout = (LinearLayout)convertView.findViewById(R.id.day_event_layout);

        //layout.addView(new EventFrame(parent.getContext()));

        TextView create = new TextView(DayViewActivity.this);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 62, getResources().getDisplayMetrics()), 1.0f);
        params.topMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
        params.bottomMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
        create.setLayoutParams(params);
        create.setBackgroundColor(Color.BLUE);
        create.setText("Test"); 
        //the following is my original LinearLayout.LayoutParams for correctly setting the TextView Height
        //new LinearLayout.LayoutParams(0, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, getResources().getDisplayMetrics()), 1.0f)   
        if(position == 0) {
            Log.d("DayViewActivity", "This should only be running when position is 0. The position is: "+position);
            layout.addView(create);
        }

        return convertView;
    }

}


Problem is when I scroll, this happens, and not on position 0... Looks like position 6 and position 8, plus it puts two in position 8. Now I am still trying to get the hang of using ListView and GridView so I do not understand why this is happening. One of the main reasons I am making this question is to help others who probably don't know about ListView and GridView's recycling View, or the way this articleputs it, ScrapView mechanism.

问题是当我滚动时,会发生这种情况,而不是在位置 0...看起来像位置 6 和位置 8,加上它在位置 8 上放了两个。现在我仍在尝试使用 ListView 和 GridView,所以我这样做不明白为什么会这样。我提出这个问题的主要原因之一是帮助其他可能不了解 ListView 和 GridView 的回收视图的人,或者本文所说的 ScrapView 机制。

enter image description here

在此处输入图片说明

Later Edit

稍后编辑

Adding link to a google IO talk that is basically all you need to understand how ListView works. Link was dead in on of the comments. So user3427079 was nice enough to update that link. Hereit is for easy access.

添加指向 google IO 演讲的链接,这基本上是您了解 ListView 如何工作所需的全部内容。链接死在评论中。所以 user3427079 非常好,可以更新该链接。是为了方便访问。

回答by Muhammad Babar

Initially, I was also unaware of listview recycling and the convertview usage mechanism, but after a whole days research I pretty much understand the mechanisms of the list view by referring to an image from android.amberfogenter image description here

最初,我也不知道 listview 回收和 convertview 使用机制,但经过一整天的研究,我通过参考来自android.amberfog的图像几乎理解了列表视图的机制在此处输入图片说明

Whenever your listview is filled by an adapter it basically shows the number of Rowsthat the listview can show on screen and the number of rows doesn't increase even when you scroll through the list. This is the trick android uses so that listview works more efficiently and fast. Now the inside story of listview referring to the image, as you can see, initially the listview has 7 visible items, then, if you scroll up until item 1 is no longer visible, getView() passes this view (i.e item1) to the recycler and you can use

每当你的列表视图通过适配器填充它基本上显示的数目的列表视图可以显示在屏幕上的行数,当您在列表中滚动也不增加。这是 android 使用的技巧,以便 listview 更有效、更快速地工作。现在listview的内幕指的是图片,正如你所看到的,最初listview有7个可见的项目,然后,如果你向上滚动直到项目1不再可见,getView()将这个视图(即item1)传递给回收商,你可以使用

System.out.println("getview:"+position+" "+convertView);

inside your

在你的里面

public View getView(final int position, View convertView, ViewGroup parent)
{
    System.out.println("getview:"+position+" "+convertView);
    ViewHolder holder;
    View row=convertView;
    if(row==null)
    {
        LayoutInflater inflater=((Activity)context).getLayoutInflater();
        row=inflater.inflate(layoutResourceId, parent,false);

        holder=new PakistaniDrama();
        holder.tvDramaName=(TextView)row.findViewById(R.id.dramaName);
        holder.cbCheck=(CheckBox)row.findViewById(R.id.checkBox);

        row.setTag(holder);

    }
    else
    {
        holder=(PakistaniDrama)row.getTag();
    }
            holder.tvDramaName.setText(dramaList.get(position).getDramaName());
    holder.cbCheck.setChecked(checks.get(position));
            return row;
    }

You will notice in your logcat, initially, convertview is null for all the visible rows, because initially there were no views (i.e items) in the recycler, so your getView() creates a new view for each of the visible items, but the moment you scroll up and item 1 moves out of the screen, it will be sent to the Recyclerwith its present state (for example the TextView 'text' or in mine case, if checkbox is checked, it will be associated with the view and stored in recycler).

您会在 logcat 中注意到,最初,所有可见行的 convertview 为 null,因为最初回收器中没有视图(即项目),因此您的 getView() 为每个可见项目创建了一个新视图,但是当您向上滚动并且项目 1 移出屏幕时,它将以其当前状态发送到Recycler(例如 TextView 'text' 或在我的情况下,如果选中复选框,它将与视图相关联,并且储存在回收站)。

Now when you scroll up/down, your listview is not going to create a new view, it will use the view which is in your recycler. In your Logcatyou will notice that the 'convertView' is not null, its because your new item 8 will be drawn using convertview, i.e., basically it takes item 1 view from the recycler and inflates item 8 in its place, and you can observe that in my code. If you had a checkbox and if you check it at position 0(let's say item1 had a checkbox and you checked it) so when you scroll down you will see item 8 checkbox already checked, this is why listview is re using the same view, not creating a new for you due to performance optimization.

现在,当您向上/向下滚动时,您的列表视图不会创建新视图,它将使用回收器中的视图。在您的Logcat 中,您会注意到“convertView”不为空,这是因为您的新项目 8 将使用 convertview 绘制,即,基本上它从回收器中获取项目 1 视图并将项目 8 充气到其位置,您可以观察在我的代码中。如果您有一个复选框,并且您在位置 0 选中它(假设 item1 有一个复选框并且您选中了它),那么当您向下滚动时,您将看到 item 8 复选框已被选中,这就是 listview 重新使用相同视图的原因,由于性能优化,不会为您创建新的。

Important things

重要的事

1. Never set the layout_heightand layout_widthof your listview to wrap_contentas getView()will force your adapter to get some child for measuring the height of the views to be drawn in list view and can cause some unexpected behaviour like returning convertview even the list is not scrolled.always use match_parentor fixed width/height.

1. 永远不要将您的列表视图的layout_height和设置layout_widthwrap_contentasgetView()会迫使您的适配器获取一些子项来测量要在列表视图中绘制的视图的高度,并且可能导致一些意外行为,例如即使列表未滚动也返回 convertview。始终使用match_parent或固定宽度/高度。

2. If you want to use some Layout or view after your list view and question might came in your mind if i set the layout_heightto fill_parentthe view after list view will not show up as it goes down the screen, so its better to put your listview inside a layout.For example Linear Layout and set the height and width of that layout as of your requirement and make the heightand widthattribute of your listview to as of your layout(like if your layout width is 320and height is 280)then your listview should have same heightand width. This will tell getView() of exact height and width of views to be rendered, and getView() won't call again and again some random rows, and other problems like returning convert view even before scrolling won't happen, i have test this myself, unless my listview was inside the lineaLayout it was also having problems like repeating view call and convert view as, putting Listview inside LinearLayout worked like magic for me.(didn't know why)

2. 如果你想在你的列表视图之后使用一些布局或视图,如果我将列表视图设置layout_heightfill_parent视图之后,你可能会想到这个问题,因为它不会在屏幕上显示出来,所以最好把你的列表视图放在一个布局。例如线性布局并根据您的要求设置该布局的高度和宽度,并使您的列表视图的高度宽度属性与您的布局相同(例如您的布局宽度为320,高度为280)然后您的列表视图应该有相同的高度宽度. 这将告诉 getView() 要呈现的视图的确切高度和宽度,并且 getView() 不会一次又一次地调用一些随机行,并且其他问题,例如在滚动之前返回转换视图也不会发生,我有测试这就是我自己,除非我的 listview 在 lineaLayout 内,否则它也有问题,例如重复视图调用和将视图转换为,将 Listview 放入 LinearLayout 对我来说就像魔术一样。(不知道为什么)

01-01 14:49:36.606: I/System.out(13871): getview 0 null
01-01 14:49:36.636: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.636: I/System.out(13871): getview 1 android.widget.RelativeLayout@406082c0
01-01 14:49:36.646: I/System.out(13871): getview 2 android.widget.RelativeLayout@406082c0
01-01 14:49:36.646: I/System.out(13871): getview 3 android.widget.RelativeLayout@406082c0
01-01 14:49:36.656: I/System.out(13871): getview 4 android.widget.RelativeLayout@406082c0
01-01 14:49:36.666: I/System.out(13871): getview 5 android.widget.RelativeLayout@406082c0
01-01 14:49:36.666: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.696: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.706: I/System.out(13871): getview 1 null
01-01 14:49:36.736: I/System.out(13871): getview 2 null
01-01 14:49:36.756: I/System.out(13871): getview 3 null
01-01 14:49:36.776: I/System.out(13871): getview 4 null

But now its solved, I know, I'm not that good at explaining but as i put my whole day to understand so i thought other beginners like me can get help of my experience and i hope now you people will have a little bit understanding of ListViewframework how it works, as it is really messy and tricky so beginners found too much problem understanding it

但是现在它解决了,我知道,我不太擅长解释,但是因为我花了一整天的时间来理解所以我认为其他像我这样的初学者可以从我的经验中获得帮助,我希望现在你们能有所了解的ListView的框架,它是如何工作的,因为它是非常的混乱和麻烦,新手发现太多问题理解它

回答by Ahmed Adel Ismail

Take care, in the Holder pattern, if you set the postion in your Holder object, you should set it every time , for example :

注意,在 Holder 模式中,如果你在 Holder 对象中设置了位置,你应该每次都设置它,例如:

@Override
public final View getView(int position, View view, ViewGroup parent) {
    Holder holder = null;
    if (view == null) {
        LayoutInflater inflater = (LayoutInflater) App.getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = inflater.inflate(getContainerView(), parent, false);
        holder = getHolder(position, view, parent);
        holder.setTag(tag);
        view.setTag(holder);
    } else {
        holder = (Holder) view.getTag();
    }
    holder.position = position;
    draw(holder);
    return holder.getView();
}

this is an example from an abstract class, where

这是一个抽象类的例子,其中

getHolder(position, view, parent);

does all the setting operations for the

完成所有的设置操作

ImageViews, TextViews, etc..

回答by Krishna

Using the Holder pattern you can achieve what you want:

使用 Holder 模式,您可以实现您想要的:

You can find description of this pattern here:

您可以在此处找到此模式的说明:

Recycling of list view happens when you scroll down the screen and above list view items are hidden . They are reused to show new list view items.

当您向下滚动屏幕并且上面的列表视图项被隐藏时,会发生列表视图的回收。它们被重用以显示新的列表视图项。