【问题标题】:How ListView's recycling mechanism worksListView 的回收机制是如何工作的
【发布时间】:2012-08-10 08:19:39
【问题描述】:

所以我之前也遇到过这个问题,自然而然就在here寻求帮助。 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;
    }

}

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

稍后编辑

添加到谷歌 IO 谈话的链接基本上是您了解 ListView 工作原理所需的全部内容。林克死在了cmets中。所以 user3427079 很好地更新了那个链接。 Here方便访问。

【问题讨论】:

  • 哈哈,谢谢你的链接。现在开始看:)
  • 您没有完全实现上一个问题中的代码示例。这个想法是你总是需要一段代码来恢复你对其他行所做的更改(在你的情况下是位置0)。
  • 嗯,我理解@Luksprog 但这是否解释了为什么它也将视图放在其他位置?如果有的话,我希望只有位置 0 有重复的视图。 Idk,仍然无法理解 ListView 是如何绑定这些东西的:/
  • 回收意味着刚刚从屏幕上消失的行视图(例如位置0,向下滚动一行后)可以在以后ListView需要显示新行时使用(如继续向下/向上滚动)。问题是刚刚消失并将在未来需要的位置使用的视图已经添加了TextView,这就是问题所在。要解决这个问题,如果 getView 方法被调用为除 0 之外的任何其他位置,则必须在 getView 方法中将其删除。
  • 置顶帖子的链接失效了,我想是这样吗? youtube.com/watch?v=N6YdwzAvwOA

标签: android android-listview android-gridview


【解决方案1】:

一开始我也不知道listview的回收和convertview的使用机制,但是经过一整天的研究,通过参考android.amberfog的一张图,我已经非常了解listview的机制了

每当您的列表视图被适配器填充时,它基本上会显示列表视图可以在屏幕上显示的 行数 数,并且即使您滚动浏览列表,行数也不会增加。这是 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 对于所有可见行都是空的,因为最初在回收器中没有视图(即项目),所以您的 getView() 为每个可见项目创建一个新视图,但是当您向上滚动并且项目 1 移出屏幕时,它将以其当前状态发送到 Recycler(例如 TextView 'text' 或在我的情况下,如果复选框是选中后,它将与视图关联并存储在回收器中)。

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

重要的事情

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

2。如果您想在列表视图之后使用一些布局或视图,如果我将layout_height 设置为fill_parent,则可能会出现问题将您的列表视图放入布局中。例如线性布局并根据您的要求设置该布局的高度和宽度,并使列表视图的 heightwidth 属性为从您的布局开始(例如,如果您的布局宽度为 320 且高度为 280) 那么您的列表视图应该具有相同的 heightwidth 。这将告诉 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 框架的工作原理有一点了解,因为它真的很混乱和棘手,所以初学者发现理解它有太多问题

【讨论】:

  • “我知道我不擅长解释” >>> 这个答案的 +37 票告诉我你在解释方面做得很好。请继续分享您的知识! ;)
  • 很好的答案 raja ji +1 我..:)
  • 感谢您提供图片。刚开始用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 回收的最佳帖子!
  • @MuhammadBabar 是的,我已经发布了问题:stackoverflow.com/q/30122027/1318946
  • @MuhammadBabar 很棒的解释人!它节省了我的时间。只是想知道,RecyclerView 是否也适用于相同的机制?在我的问题stackoverflow.com/questions/47897770/… 中,我知道listView 是不可能完成的任务。我应该尝试使用recyclerView 吗?
【解决方案2】:

注意,在 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..

【讨论】:

  • 没有注意到我一直在做。 parent.setTag(holder); 而不是正确的方式,即view.setTag(holder);
【解决方案3】:

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

You can find description of this pattern here:

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

【讨论】:

    猜你喜欢
    • 2010-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多