【问题标题】:Knowing when a View in a ListView has gone off the screen?知道 ListView 中的视图何时离开屏幕?
【发布时间】:2013-10-27 17:42:53
【问题描述】:

我用谷歌搜索了这个,但找不到答案,所以这里......

我有一个显示一些文本和图像的 ListView。底层适配器出于性能原因(根据推荐的方法)回收视图,并且我根据找到的推荐使用 AsynchTask 加载图像 on the Android Developers site's bitmap page

一切都完美而顺利,但我有一个问题。当适配器中的 View 被回收时,它仍然有对旧图像(ImageView)的引用。如果用户滚动缓慢,则 AsynchTask 有足够的时间来加载新图像并显示它,因此用户看不到新图像的重新加载。

但是,如果用户滚动得非常快,加载图像的延迟意味着他们在被新图像替换之前看到旧图像(当视图被另一个项目使用时加载)。

所以,我的问题是,我如何才能检测到 View 何时在屏幕上不再可见,以便我可以删除图像?这意味着用户会看到一个空的列表视图项目,该项目最终将加载适当的图像,这样看起来会更好。

提前非常感谢。这是列表视图适配器代码。

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
    View view = convertView;
    ListViewHolder viewHolder;

    // if this is not a recycled view then create a new view for the data...
    if (view == null)
    {
        view = this.inflater.inflate(R.layout.target_list_view_layout, null, true);

        viewHolder = new ListViewHolder();

        viewHolder.manufacturer = (TextView) view.findViewById(R.id.manufacturer);
        viewHolder.targetName = (TextView) view.findViewById(R.id.targetName);
        viewHolder.targetThumbnail = (ImageView) view.findViewById(R.id.targetThumbnail);

        view.setTag(viewHolder);
    } else
    {
        viewHolder = (ListViewHolder) convertView.getTag();
    }

    TargetDescriptor targetDescriptor = this.selectedTargets.get(position);

    viewHolder.manufacturer.setText(targetDescriptor.manufacturer);
    viewHolder.targetName.setText(targetDescriptor.targetName);

    // At this point I pass the image view reference to my background task to load the image
    LoadImageViewAsynchTask loadImageTask = new LoadImageViewAsynchTask(viewHolder.targetThumbnail, targetDescriptor);
    loadImageTask.execute(new Integer[]
    { 64, 64 });

    return view;
}

编辑:如果有帮助,那些使用 eBay Android 应用程序的人可以看到我正在寻找的效果。

【问题讨论】:

标签: android image listview asynchronous


【解决方案1】:

实际上有一个更简单的方法可以做到这一点,你可以使用监听器:

mListView.setRecyclerListener(new AbsListView.RecyclerListener() {
@Override
    public void onMovedToScrapHeap(View view) {
  }
});

【讨论】:

  • 这实际上是“知道 ListView 中的视图何时离开屏幕?”这个问题的最佳答案。但看起来原作者真的只需要知道视图何时被回收。
【解决方案2】:

不敢相信我是多么愚蠢,这是一个非常容易解决的问题!

如果视图被重新循环,基本上在我的适配器中,我只需要在重新使用视图之前将 ImageView 位图设为空。这意味着在加载下一个图像之前,列表视图项中的图像是空白的。在后台加载新图像时,我不再遇到旧图像存在的问题。

我还对适配器进行了更改,取消了任何当前绑定到视图的 AsynchTask,该视图可能仍在加载不再需要的先前图像。当我在列表视图中快速滚动时,这种情况非常普遍,通常会备份两个或更多图像加载,因此图像会在确定正确图像之前更改几次。

这里是新代码,对更改进行了注释,以便您了解发生了什么:

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
    ListViewHolder viewHolder;

    // if this is not a recycled view then create a new view for the data...
    if (convertView == null)
    {
        convertView = this.inflater.inflate(R.layout.target_list_view_layout, null, true);

        viewHolder = new ListViewHolder();

        viewHolder.manufacturer = (TextView) convertView.findViewById(R.id.manufacturer);
        viewHolder.targetName = (TextView) convertView.findViewById(R.id.targetName);
        viewHolder.targetThumbnail = (ImageView) convertView.findViewById(R.id.targetThumbnail);

        convertView.setTag(viewHolder);
    } else
    {
        viewHolder = (ListViewHolder) convertView.getTag();

        // Cancel the previous attempt to load an image as this is going to be superceded by the next image
        viewHolder.loadImageViewAsynchTask.cancel(true);

        // Clear down the old image so when this view is displayed, the user does not see the old image before the
        // new image has a chance to load in the background
        viewHolder.targetThumbnail.setImageBitmap(null);
    }

    TargetDescriptor targetDescriptor = this.selectedTargets.get(position);

    viewHolder.manufacturer.setText(targetDescriptor.manufacturer);
    viewHolder.targetName.setText(targetDescriptor.targetName);

    LoadImageViewAsynchTask loadImageViewAsynchTask = new LoadImageViewAsynchTask(viewHolder.targetThumbnail);
    loadImageViewAsynchTask.setTargetDescriptor(targetDescriptor);
    loadImageViewAsynchTask.execute(new Integer[]
    { 64, 64 });

    // Keep a reference to the task so we can cancel it if the view is recycled next time round to prevent
    // un-neccessary image loads that are out of date
    viewHolder.loadImageViewAsynchTask = loadImageViewAsynchTask;

    return convertView;
}

【讨论】:

  • 是的。将行重置为默认值对我有用。谢谢,它有帮助。
  • @Justin Phillips:你是如何取消之前视图中运行的异步任务的?
  • @Ashwin 我为正在回收的视图调用取消方法。您可以在上面的代码清单中看到它。
【解决方案3】:

如果您为位图使用 LruCache,并且让列表视图中的每个视图保留 LruCache 项的键,您可以更好地控制它。然后,在 getView 中,您首先尝试从 LruCache 获取位图,并且仅如果它不存在,请重新下载。

另外,你可以在 ListView 上设置一个 setOnScrollListener(),并在用户第一次滚动时使用它的 visibleItemCount 参数来调整 LruCache 的大小。

【讨论】:

  • 将图像保存在缓存中的问题是我得到了 OutOfMemory 异常。所以我对 AsynchTask 加载的 ImageView 有一个弱引用,允许它被 GC'ed。
【解决方案4】:

如果您使用覆盖的getView 方法来显示图像,那么您可以检查convertView。如果它是 null 那么你可以发现它被回收或者它之前没有被初始化。 就像这样:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    // TODO Auto-generated method stub
    ViewHolder viewholder;
    if (convertView == null) {
        viewholder = new ViewHolder();
        inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.item_row, parent, false);
        viewholder.tvID = (TextView) convertView.findViewById(R.id.tvID);
        viewholder.tvName = (TextView) convertView.findViewById(R.id.tvName);
        viewholder.tvFamily = (TextView) convertView.findViewById(R.id.tvFamily);
        viewholder.ivMain = (ImageView) convertView.findViewById(R.id.ivMain);
    } else {
        viewholder = (ViewHolder) convertView.getTag();
    }
    viewholder.tvID.setText(IDs[position]);
    viewholder.tvName.setText(Names[position]);
    viewholder.tvFamily.setText(Familys[position]);
    viewholder.ivMain.setImageResource(Images[position]);
    convertView.setTag(viewholder);
    return convertView;
}

【讨论】:

  • 谢谢。我了解回收的工作原理,除了我使用 AsynchTask 在后台线程中加载图像。因此,我没有在我的“viewHolder”对象中设置 ImageView(根据您的示例),而是在适配器中触发了一个异步任务,该适配器采用 ImageView 引用并设置图像。我将使用适配器代码更新我的原始问题,以便更清楚我想要做什么。
  • 然后您可以将一些图标(如图像持有者图标)放入视图中显示,直到异步任务完成。
  • @RickFalck 谢谢,但这不是我要解决的问题。我需要识别不再实际显示的视图,以便清除它们的图像。那时我可以按照你的建议做并添加一个占位符图像,或者只是空白图像视图。
猜你喜欢
  • 1970-01-01
  • 2018-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-14
  • 2017-10-23
  • 2020-12-19
  • 2017-08-17
相关资源
最近更新 更多