【问题标题】:ListView with horizontal RecycleView get lockListView 与水平 RecyclerView 获得锁定
【发布时间】:2018-04-10 10:22:07
【问题描述】:

我有一个大约 5-6 行的 ListView,每一行都是一个显示水平片段的 RecycleView。卡片(例如 Netflix、HBO 应用)

注意事项:

  • 每个 RecycleView 都有大约 30-50 个项目。
  • 每个 ViewHolder 都有一个“大”图片作为背景(大约 300x260dp,具体取决于设备屏幕尺寸,宽度约为设备屏幕的 90%)
  • 使用 Glide 加载图像(代码如下)

我为 ListView Adapter 中的 RecycleViews 创建了一个缓存(SparseArray),以保持每个 RecycleView 的状态,否则每次离开屏幕时它都会重新启动到位置 0。

通过这样做,只需将每个 RecycleView 滚动到最后,就会使其中一些冻结。

尝试了几种解决方法:

  • 审查了 MAT 并重构了一些代码以避免由于静态上下文而泄漏
  • MAT 显然会因为图像而显示大量内存
  • 缩小图片尺寸
  • 隐藏图片
  • 创建 ViewHolders 缓存(无效)
  • Glide 下载后为图像创建内存缓存
  • 将 ViewHolders setIsRecyclable 设置为 false

但是,这些选项都不起作用,保证应用程序不会冻结的唯一方法是避免 ListView 缓存(如果您将视图移出屏幕,则会丢失您访问的最后一个位置)

Glide Code(我在这里尝试了几个选项:避免 Bitmap、避免内存缓存、将磁盘 Strategy 更新为 ALL)

Glide.with(context).load(url)
                .asBitmap()
                .skipMemoryCache(true)
                .thumbnail(0.5f)
                .centerCrop()
                .error(placeHolderId)
                .diskCacheStrategy(DiskCacheStrategy.RESULT)
                //.priority(Priority.NORMAL)
                .dontAnimate()
                .into(new SimpleTarget<Bitmap>() {
                    @Override
                    public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
                        if (bitmap != null) {
                            imgView.setImageBitmap(bitmap);
                            //cache.put(url, bitmap);
                        } else {
                            imgView.setBackground(null);
                        }
                    }
                });

有什么建议/建议吗? 也许还有另一种选择可以在没有缓存的情况下保持 View 的状态?

ListView 适配器代码:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
            case TYPE_VENUE_FILTER: {
            View v;
            // Create a new view into the list.
            LayoutInflater inflater = (LayoutInflater) context
                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            if (inflater != null) {
                v = inflater.inflate(R.layout.fragment_venue_filter, parent, false);
            }

            // We need to get the exact filter to be displayed
            int index = position - FIRST_VENUE_FILTER_ROW;
            if (index < this.filters.size()) {
                VenueFilter filter = this.filters.get(index);

                // Recalculate the distance order
                filter.sortVenuesForLocation(this.lastLocation);

                // Set data into the view.
                VenueFilterFragment.loadFragment(this.context, v, filter, this.fm);
            }

            return v;
        }
}

2004 年 11 月更新

我终于明白了。

  • 按照建议重用适配器上的 converView
  • 在 RecycleView 中包含一个 ScrollListener 并将最后一个滚动位置存储在 bean 中,以便在重新访问时恢复它。

此代码在适配器中,用于对特定行进行充气(getView)

    // We should reset or recover the scroll state
    LinearLayoutManager llm = (LinearLayoutManager) filterContent.getLayoutManager();
    if (filter.offset > 0) {
        if (llm != null) llm.scrollToPosition(filter.offset + 1);
    } else {
        if (llm != null) llm.scrollToPosition(0);
    }

    // We need to clear every time. Because we are reusing the view
    recycleView.clearOnScrollListeners();

    // We need to create a new specific scroll listener
    recycleView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            // Keeping the offset for a future load
            LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
            if (llm != null) filter.offset = llm.findFirstVisibleItemPosition();
        }
    });

【问题讨论】:

  • 请输入您的适配器代码
  • 添加 ListView 适配器代码@CaoMinhVu,感谢关注
  • 在我看来有两件事非常可疑:1)您根本没有使用convertView,即您从不重用来自适配器的视图并且没有利用回收逻辑,这将给你的性能放缓。在这种情况下,您必须为每个项目增加视图,这是一项昂贵的操作。 2)参考java命名约定,我假设VenueFilterFragment是一个类名,因此loadFragment是一个奇怪的静态方法..这样做的目的是什么?
  • 我在 ListView 中对不同类型的单元格使用 Fragments,这个 Fragments 可以用于应用程序的其他部分。静态方法仅用于在给定视图上设置内容。 (但现在作为片段执行,因为我丢失了所有片段生命周期)。与第 1 点相关,您是对的,现在正在研究它。
  • 这对我来说确实听起来很奇怪。如果要为给定的片段设置内容,它应该是片段对象上的非静态方法。静态方法在片段类上设置“所谓的”状态,这意味着它是某种共享数据。我看到您在每次 getView 调用时都将一个过滤器对象传递给这个共享状态,这对我来说真的没有多大意义。我想不出任何正确的情况。请重新考虑这个逻辑,你很可能不想在那里进行静态调用。

标签: android listview android-recyclerview android-glide


【解决方案1】:

这是一个非常广泛和复杂的问题。您的布局很复杂,因此需要一些努力才能使其高效。您可以采取哪些措施来提高性能:

  • 所有RecyclerViews 都应尽可能使用视图持有者。如果需要,定义多个视图类型。父 RecyclerView 可能只有一种视图类型,即带有子 RecyclerView 的项目。孩子RecyclerViews 可能也可以通过RecycledViewPool 分享视图持有者。更多信息在这里:https://developer.android.com/reference/android/support/v7/widget/RecyclerView.RecycledViewPool.html
  • 确保所有布局尽可能平坦。您拥有的视图类型越多,这一点就越重要。
  • Glide 是加载图像的好选择。确保尽可能使用缓存。图片下载后缓存,减少对网络的影响。下载图像时使用占位符或进度条。重用视图时重置每个ImageView 状态以避免闪烁。
  • 取消不再需要加载的项目的图像加载。这也应该有助于您减少社交活动。
  • 你也可以恢复滚动位置,这里应该没有问题。这是一个单独的主题,您可以参考此主题了解更多详细信息:How to save RecyclerView's scroll position using RecyclerView.State?
  • 并留意 MAT,是的。确保您不使用太多资源很重要,这在这种情况下非常容易。

我希望这会有所帮助。祝你好运!

【讨论】:

  • 谢谢!我刚刚添加了我的 ListView 适配器代码,可能是因为我加载包含 RecycleView 的片段的方式,您在最后一个链接中建议的方法不起作用(因此,onSaveState ... 没有被调用)将研究其余部分,或者也许找到另一种管理片段生命周期的方法。最好的!
猜你喜欢
  • 1970-01-01
  • 2019-01-20
  • 2015-04-12
  • 1970-01-01
  • 2016-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多