ListView的回收机制如何工作


146

因此,我遇到了以前遇到的问题,很自然地我在这里寻求帮助。Luksprog的答案很好,因为我不知道ListView和GridView如何通过回收Views进行优化。因此,根据他的建议,我能够更改将视图添加到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;
    }

}

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

在此处输入图片说明

以后编辑

添加链接到google IO对话,基本上就是了解ListView的工作原理所需要的。链接对评论不屑一顾。因此,user3427079足以更新该链接。是为了方便访问。


哈哈,感谢您的链接。现在就开始观看它了:)
Andy

您没有完全实现上一个问题的代码示例。这个想法是,您总是需要一段代码来还原对其他行所做的更改(在您的情况下为position 0)。
Luksprog

好吧,我明白@Luksprog,但这能解释为什么它还将View置于其他位置吗?如果有的话,我希望只有位置0具有重复的视图。Idk,仍然难以理解ListView如何绑定这些东西:/
Andy

1
回收意味着刚从屏幕上消失的行视图(例如,向下滚动一行后的位置为0)可以在以后ListView需要显示新行(例如继续向下滚动/向上滚动)时使用。问题在于,刚刚消失的视图将用于将来的所需位置TextView,这已经成为问题。要解决该问题,必须在getView方法中getView除之外的其他位置调用该方法,然后将其删除0
Luksprog

2
热门帖子的链接已死,我认为是吗?youtube.com/watch?v=N6YdwzAvwOA
G_V 2014年

Answers:


330

最初,我也没有意识到listview的回收和convertview的使用机制,但是经过一整天的研究,我通过引用来自android.amberfog的图像几乎了解了list view的机制。 在此处输入图片说明

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

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

在你里面

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

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

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

重要的事

1。永远不要将listview 的layout_heightand 设置layout_widthwrap_contentas,这getView()将迫使适配器让孩子去测量要在列表视图中绘制的视图的高度,并且可能导致一些意外行为,例如即使列表未滚动也要返回convertview。总是使用match_parent或修复宽度/高度。

2。如果你想使用一些布局或查看您的列表视图和问题可能以后就在你的心中,如果我设置layout_heightfill_parent列表视图后视图将不会显示为它的股价下跌了屏幕,所以它能够更好地把你的ListView内例如Linear Layout并根据需要设置该布局的高度宽度,并将listview 的heightwidth属性设置为您的布局(例如,如果您的布局宽度为320而height为280),则您的listview应该具有相同的高度宽度。这将告诉getView()确切的高度和要渲染的视图的宽度,并且getView()不会一次又一次地调用一些随机行,以及其他问题,例如即使在滚动之前也不会返回转换视图,我已经进行了测试这个我自己,除非我的列表视图位于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

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


36
“我知道我不太擅长于解释” >>>这个答案的+37票告诉我,您在解释方面做得很好。请继续分享您的知识!;)
2Dee 2014年

1
我的好答案raja ji +1 .. :)
Ehsan Sajjad 2014年

2
谢谢您的图片。在我开始使用Android时,在使用ListView遇到很多麻烦之后,经过这么多的努力,我已经知道了回收原理。尽管如此,还是拍下了这张照片和一句话:if you had a checkbox and if you check it at position 0(let say item1 has also a checkbox and you checked it) so when you scroll down you will see item 8 checkbox is already checked,this is why listview is re using the same view not creating a new for you due to performance optimization.让我意识到自己的错误。我遇到了有关Android ListView回收的最佳文章!
凯文·克鲁伊森

1
@MuhammadBabar是的,我已经发布了问题:stackoverflow.com/q/30122027/1318946
Pratik Butani 2015年

1
@MuhammadBabar很棒的解释者!它节省了我的时间。只想知道,它们RecyclerView也可以在相同的机制上工作吗?在我的问题stackoverflow.com/questions/47897770/…中,我知道使用不可能完成任务listView。我应该尝试recyclerView吗?
Shambhu

8

请注意,在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();
}

这是一个抽象类的例子

getHolder(position, view, parent);

进行所有的设置操作

ImageViews, TextViews, etc..

1
没注意到我一直在做这些事情。parent.setTag(holder);而不是正确的方法是view.setTag(holder);
ArtiomLK

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.