快速滚动时,getView调用错误的位置

时间:2012-03-15 00:43:17

标签: android scroll android-listview

相当新的Android开发人员。

我遇到了一个奇怪的问题,我不确定如何解决。我在这里看到了很多问题,听起来我们遇到了同样的问题,但他们问题的解决方案似乎从未适用于此处发生的事情。

我有一个自定义listView设置使用扩展BaseAdapter并使用视图持有者的单独类(我设置了这个查看自定义列表视图的各种不同示例)。它有一个文本视图,一个按钮,一个进度条和一个图像按钮,这些项目是根据用于下载文件的AsyncTask隐藏/更改的。

当我慢慢滚动列表时,一切正常。当我快速向上/向下滚动列表时,就好像列表中某些单元格的视图被重新分配给其他单元格一样。

我把这行放在我的适配器类中的“getView”方法的顶部:

    @Override
    public View getView(final int pos, View convertView, ViewGroup parent)
    {
        Log.i("ks3","getView called, position is " + pos);

我的列表中有7个项目,其中6个适合屏幕。首次显示时,我会在日志中看到:

getView called, position is 0
getView called, position is 1
getView called, position is 2
getView called, position is 3
getView called, position is 4
getView called, position is 5

当我慢慢向下滚动到下一个项目时,接下来打印出来:

getView called, position is 6

当我向上滚动时,这个:

getView called, position is 0

慢慢地向上和向下缓慢地产生这些结果。

当我开始快速来回滚动时,它会多次打印出这条消息,大多数时间显示6和0作为位置,但偶尔我也会看到这些:

getView called, position is 1
getView called, position is 5

不应调用位置1和5,因为它们始终在屏幕上。就好像getView一样混乱了。与此同时,正如我之前所说,我的名单看起来很奇怪。图像按钮将在不应该移动的单元格中向上或向下移动。

如果我回去顺利滚动,我会再次看到0和6的位置。

老实说,我不确定如何解决这个问题。我想也许我可以限制你滚动列表的速度有多快,但是找不到任何有用的东西。

谢谢!

编辑: 我想更新一些关于这个问题的事情。第一个是,从这里的评论和listView上的谷歌视频,我注意到getView可以被调用其他东西然后我想象的那样,例如测量,所以我不应该惊慌通过我最初认为是我的问题的一部分(因为我认为getView被调用错误的位置)。

其次,我多次看到“在适配器中缓存视图”是一个非常糟糕的主意。我不清楚这意味着什么,但我很确定这是我做错的事情之一(我保存了一个按钮实例,progressBar,imageButton ......)。

那个,以及我更新getView以外的视图的事实,我不使用notifyDataSetChanged();这些可能会导致一些不稳定的事情发生。

5 个答案:

答案 0 :(得分:13)

ListView在屏幕上的不同位置重复使用视图是正确的。这是一种优化,通过不分配新的视图来保持内存使用的合理性和快速性。

您可能错误地使用LiewView。观看this talk on how to properly use ListView以了解整个故事,但这里是重点:

  1. “位置”是指数据在适配器列表中的位置。如果适配器数据在数组中,那么这将是该数组的索引。
  2. “ID”是指数据本身的值。如果您有一个名单并使用它们,他们的位置将会改变,但他们的ID不会改变。
  3. “索引”是指视图相对于可视屏幕区域的相对位置。你可能永远不需要这个。
  4. 不要在适配器的getView()方法之外操纵视图(或尝试缓存它们),否则会出现奇怪的行为。
  5. 如果对getView(int, View, ViewGroup)的调用提供了一个视图实例,请填充其字段而不是全新的视图。假设您已正确实施getItemType(),您将始终获得正确的View类型以重新填充。
  6. 尽可能快地制作getView()方法,并且只对其他线程进行繁重的工作。
  7. 仅仅因为名为getView()的容器并不一定意味着将显示数据。框架将这些用于测量目的。由于工作可能会被丢弃,这是确保getView()尽可能快地完成工作的另一个原因。
  8. 如果您需要在屏幕上显示某些数据,请说明您的下载已完成,就在您致电notifyDataSetChanged()时。不要直接使用视图,它们将在重新绘制时在下一个UI循环中填充。
  9. 刚刚花了几天时间重新编写一个天真实现的ListView,我感到很痛苦。但结果是值得的!

答案 1 :(得分:4)

始终在getview中使用此方法: convertView = inflater.inflate(R.layout.listinflate,parent,false);

让您的活动实现onScrollListener,当用户投掷时,通知您的listview适配器,从不介意正确地努力更新项目。当用户不再投掷时告诉适配器注意在getView中提供数据。

这解决了我所有的问题。 还有一件事不在列表视图高度中使用wrap_content。在其中也设置了smoothscroll false。

答案 2 :(得分:1)

我有同样的问题:进入适配器我在getView方法中改变了背景颜色,但有时listview是“reciclyng”视图(得到错误的背景)。 我解决了简单地将“else”放到每个

if(...){changeBackground}

我添加了

else {restore default background}

然后它运作顺利

答案 3 :(得分:0)

为每个需要绘制但之前在屏幕上不可见的列表项调用

getView。如果你快速滚动你可能会得到奇怪的位置,但这不应该导致你所描述的错误(我猜Android在这里没有错误,因为那些ListViews经过了很好的测试)。例如。当您滚动得足够快时,您可能需要绘制2个视图。 也许您的代码中还有其他问题。

答案 4 :(得分:0)

记录并扩展@Mark的答案

Android完成下一个任务。让我们说我有一个包含100个元素的列表。如果您正在加载,则会调用getview元素0,1,2,3 ...到10。如果我们继续,下一个位置将是11.然而,位置11回收与位置0相同的视图。因此,位置11的视图存在但是它具有错误的值(0值)。

答案:如果视图为空,则修改数据。如果视图为null,则缩小并修改视图的值。如果没有,则修改视图的值。

假设我有一个带有单个textview的item_view(布局),那么适配器应如下所示:

正确的版本:

public class XXXAdapter extends ArrayAdapter<XXX> {....
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
   if (convertView==null) {
       LayoutInflater inflater = LayoutInflater.from(this.getContext());
       convertView = inflater.inflate(R.layout.**itemofthelayout**, parent, false);
   }
   ***Object** item=this.getItem(position);
   TextView txt= (TextView) convertView.findViewById(R.id.**idsometextviewinsidelayout**);
   txt.setText(String.valueOf(position));
   return convertView;
   // return super.getView(position, convertView, parent);
}

错误的版本:

public class XXXAdapter extends ArrayAdapter<XXX> {....
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
   if (convertView==null) {
       LayoutInflater inflater = LayoutInflater.from(this.getContext());
       convertView = inflater.inflate(R.layout.**itemofthelayout**, parent, false);
       ***Object** item=this.getItem(position);
       TextView txt= (TextView) convertView.findViewById(R.id.**idsometextviewinsidelayout**);
       txt.setText(String.valueOf(position));
       return convertView;
       // return super.getView(position, convertView, parent);
   }
}