ListView的回收机制如何运作

时间:2012-08-14 03:54:56

标签: android android-listview android-gridview

所以我遇到过这个问题,我自然就here寻求帮助。 Luksprog的答案很棒,因为我不知道ListView和GridView如何通过回收视图优化自身。因此,根据他的建议,我能够改变将Grid添加到GridView的方式。问题是现在我有一些没有意义的事情。这是我getView的{​​{1}}:


BaseAdapter

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

enter image description here

稍后修改

添加指向Google IO谈话的链接,基本上只需要了解ListView的工作原理。链接已经落在了评论上。所以user3427079非常适合更新该链接。 Here这是为了方便访问。

4 个答案:

答案 0 :(得分:314)

最初我还没有意识到listview回收和convertview使用机制,但经过一整天的研究后,我通过引用来自android.amberfog的图像非常了解列表视图的机制。 enter image description here

当你的listview填充了适配器时,它基本上显示了listview可以在屏幕上显示的的数量,即使你滚动列表,行数也不会增加,这是android使用的技巧,使listview工作更有效,更快, 现在listview的内幕故事引用图像,你可以看到最初列表视图有7个可见项目,如果向上滚动,当项目1不再可见时,getView()将此视图(item1)传递给recycleler,你可以使用

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,因为最初在recycleler中没有视图(item),所以你的getView()为可见项创建每个新视图,但是你滚动的那一刻您的项目1将发送到 Recycler 与其状态(例如TextView文本和我的情况,如果选中复选框,它也将与视图相关联并存储在回收站中)。 现在,当您向上/向下滚动列表视图不会创建新视图时,它将使用已在回收器中的视图(转换视图),在 Logcat 中您会注意到convertView不是null,因为你的新项目8将使用convertview绘制,即,基本上它取得了来自回收器和inflater的item1视图而不是项目8,你可以在矿区代码中观察,如果你有一个复选框,如果你在位置0检查它(假设item1也有一个复选框,你检查了它)所以当你向下滚动时你会看到第8项复选框已被选中,这就是为什么listview重新使用相同的视图而不是为你创建一个新的性能优化。

重要事项

<强> 1 即可。切勿将列表视图的layout_heightlayout_width设置为wrap_content,因为getView()会强制您的适配器获取一些子项来测量要在列表视图中绘制的视图的高度并且可能会导致一些意外的行为,比如返回convertview,即使列表没有滚动。总是使用match_parent或固定的宽度/高度。

<强> 2 即可。如果您想在列表视图和问题后使用某些布局或视图,如果我将layout_height设置为fill_parent,列表视图后的视图将不会显示在屏幕上,最好将listview放在布局中。例如,线性布局并根据需要设置该布局的高度和宽度,并使高度宽度属性您的列表视图的布局(如果您的布局宽度 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

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

答案 1 :(得分: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..

答案 2 :(得分:2)

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

You can find description of this pattern here:

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

答案 3 :(得分:0)

Recyclerview只是一个包装器类,以高效的方式(即通过重用已经创建的视图)来管理动态视图的添加。

仅考虑recyclerview适配器,调用方法的顺序是-

  • getItemCount-首先调用它,以便适配器可以知道需要创建或管理多少个视图。
  • getItemViewType-接下来,将其调用以了解需要放大哪种类型的视图。
  • onCreateViewHolder-接下来,将其调用以了解要放大的视图。
  • onBindViewHolder-进入此步骤适配器将为您提供Viewholder实例,该实例持有关于position的膨胀视图,因此您可以在视图上设置数据。