【问题标题】:Nested RecyclerView creates all ViewHolders at once嵌套 RecyclerView 一次创建所有 ViewHolders
【发布时间】:2019-08-12 16:35:52
【问题描述】:

我有一个包含嵌套 RecyclerViews 的相当复杂的列表。我知道嵌套的 RecyclerViews 不是最好的解决方案,但在我的情况下,它是创建结构化代码并满足要求的少数解决方案之一。我附上了结构的图像。你可以以电报为例来提高你对结构的理解。基本上我有一个带有项目 RV-1-Item 的外部 RecyclerView RV-1 和一个带有项目 RV-2-Item 的内部 RecyclerView RV-2。到目前为止一切顺利,我的问题是外部 RecyclerView 按预期回收视图,但是如果 RV-1-Items 之一进入视图,则创建 RV-2 的所有 ViewHolders(这意味着有时会创建超过 100 个 ViewHolders )。总而言之,我的问题是如何强制内部 RecyclerView RV-2 也回收 ViewHolders。

我知道内部 RecyclerView RV-2 必须有很高的 wrap_content 因为它取决于内部项目的数量,我也无法设置 setHasFixedHeigth(true) (我不知道它是否有帮助)因为在运行时新的 RV-2-Items 可以添加到 RV-2 中。我还尝试在 RV-2 上设置 setNestedScrollingEnabled(false),因为我在网上阅读了很多关于它的内容,但它也没有帮助我。

所以基本上这就是我的配置方式

RV-1

layoutManager = LinearLayoutManager(context)      
isNestedScrollingEnabled = false

RV-2

setHasFixedSize(true)
layoutManager = LinearLayoutManager(context).apply {
    reverseLayout = true
}

除此之外,我还有一些 ItemDecorator,但它们只在项目之间创建空间,因此它们不应该对问题做任何事情。

总结一下: 外部 RV-1 按预期回收 ViewHolders,但内部 RV-2 一次创建所有 ViewHolders,即使它们不在屏幕上。我假设是这种情况,因为 RV-2 的高度为 wrap_content,当需要测量 layout_height 时,它需要创建所有视图。 问题:有没有办法强制 RV-2 回收它的视图?

编辑: 此外,我在所有 RV-2 RecyclerViews 之间使用共享 RecycledViewPool 但这与问题并没有真正的关系,因为即使 ViewHolders 在 RecyclerViews 之间共享,RV-2 RecyclerView 也不应该创建不可见的 ViewHolders当它被初始化时。

编辑 2: 很多 cmets 和相关问题都说两个垂直嵌套的 RecyclerViews 在 android 中是不可能的,以防这个问题的所有访问者都认为我的问题是:你将如何实现这样的结构。很明显,我可以制作一个包含 IM(圆形图像视图)和 RV-2-Item 的视图,并在不需要时让 IM 不可见。在我看来,这在某种程度上使结构更加复杂。此外还有一个要求是RV-1-Item左侧的IM必须具有在RV-1-Item中上下移动的能力,这在我目前的结构下显然更容易。

编辑 3:(我承诺的最后一个) 我展示的问题可以通过使用我在编辑 2 中解释的方法来解决,即使它不是最好的解决方案。但问题是我有一个更复杂的屏幕,因为我有三个嵌套的 RecyclerViews,所以这个方法不再起作用。使用 EDIT 2 的方法,我可以将这个数字减少到两个,但我仍然会留下两个嵌套的 RecyclerView,我想不出一种解决方法可以解决剩余的两个嵌套 RecyclerView 的问题。我附上了一张更复杂的屏幕图片,其中包含带有标记部分的应用界面,以帮助您理解结构。

【问题讨论】:

  • 当回收器视图位于另一个可滚动内容中时,它会停止回收。这很容易通过将你的 recyclerview 放在 NestedScrollView 中并观察效果来测试。解决方案:不要使用嵌套滚动视图。
  • 要理解为什么会这样:在您的 RV-2 看来,尺寸是固定的,因此它知道显示其项目需要多少空间(大小为 1 * 项目数) .外部(RV-1)正在膨胀视图(您的 RV-1 项目)并要求它计算其大小。我们还没有看到您的 XML/代码,但很自然,这会导致 RV-2 继续创建视图,直到它们用完为止,因为没有人告诉它停止。这是像这样嵌套动态滚动内容的副作用。我会重新考虑解决方案(加上这对用户来说是一场噩梦)
  • @user2836202 我明白您为什么将其标记为重复,但您链接的问题已通过使用 GridLayout 解决,这在我的场景中根本不可能。因此,我认为这两个问题并不完全相似,并且有不同的解决方案。我将很快更新我的问题,提供更多信息,使我的问题更加复杂。
  • @MaxGierlachowski 你有办法解决这个问题吗?

标签: android android-recyclerview android-viewholder nestedrecyclerview


【解决方案1】:

(对于您在解决“如何不让 RecyclerView 一次创建所有项目”时的具体问题的答案并不完全正确,但最有可能通过不使用嵌套的 recyclerviews 来解决您的具体问题)

我建议(以与已经建议 in this answer 非常相似的方式)将您的提要扁平化到一个回收站视图中 (无论您如何调整嵌套的recyclerview 架构,恕我直言,它永远不会比只有一个recyclerview 的性能好,而且由于您不需要嵌套滚动(我猜),因此只有一个recycler 视图应该是您的最佳选择)。

我建议不要以数据结构的方式考虑您的提要,而是以您希望显示的方式以及如何将其拆分为“看起来相似”/由相同事物组成的较小项目.

从您的屏幕截图中,我会看到每个聊天项目的以下项目/视图类型:

  • 聊天标题(带有图标和文本“新组”的事物)
  • 用户徽章(带有文字“Jürgen”的图片)
  • 一个消息项目(一个文本气泡,例如,在底部的屏幕截图中实际上会有 3 个项目,每条消息一个)
  • 包含日期和操作/回复项的部分。

这些项目比整个聊天项目小得多,因此可以更快地创建/回收。 对于这些项目中的每一个,创建一个视图类型和一个视图持有者,并将它们视为单独的回收者视图项目。 当正确使用getItemViewType 方法时,recyclerview 将为您需要的位置创建/准备正确类型的视图。

为此,适配器需要添加一些逻辑,因为您的数据很可能会像

聊天列表,每个聊天都有一个名称和一些要显示的消息

我们需要它

前 6 个元素用于第一次聊天,其中第一个位置 是标题,第二个是用户徽章,接下来的 3 项是消息 项目,然后我们需要一个行动项目。

因此,您基本上需要计算显示每个聊天项目需要多少 recyclerview 项目,这可能是这样的计算:

1 个聊天标题项 + 1 个用户徽章项 + 3 个消息项 + 1 个操作/回复项 = 6

这个计算需要对你的数据列表中的每个聊天项分别进行。

因此,如果您的数据列表中只有这个聊天项目要显示,您实际上需要告诉适配器创建 6 个项目(在这种情况下,通过在 getItemViewCount() 返回 6)。

然后,你需要使用getItemViewType(position: Int)函数告诉适配器,在recyclerview的哪个位置,适配器需要准备哪种类型的视图。 因此,您再次需要一些逻辑来说明这一点,例如在位置 0,第一个聊天项目的聊天标题应该是,在位置 1,第一个聊天项目的用户徽章,在位置 2-4 的消息项目应该是,在位置 5 的操作项,在位置 6 的聊天标题第二次聊天应该是等等 (同样,所有聊天项目的逻辑都需要到位,并且它可能会变得非常混乱/复杂,因为要计算一个职位的每个聊天项目视图类型,例如,所有先前的聊天元素视图计数也需要重新计算(以了解您当前聊天项目从哪个回收站视图位置开始)。

由于这往往会炸毁您的适配器,因此我建议(如果您还没有这样做的话)在其中获得一些管理器/委托架构。 所以例如每个视图类型都有一个委托,以及一个管理器,它计算每个聊天项目所需的回收视图项目/视图类型的数量。

仅供参考:

前段时间我们遇到了和你类似的情况 (recycler-view 的设计类似于社交媒体提要,它应该显示提要中的前 n 个 cmets,我们显示了每个提要项目(这是一个 recyclerview 项目)的 cmets,该项目中还有另一个 recyclerview ) 并且在一些我们无法解决的性能问题之后,我们只是将回收视图变平,并且再也没有遇到性能问题。

【讨论】:

  • 嘿,首先非常感谢这个深入的回答,我很感激。你的建议听起来很好,虽然我有点难过我们要么不得不妥协视图结构或效率来获得这种结果。因为我认为更符合逻辑和更易于维护的视图结构是我展示的(只有当我想到视图上的不同圆角时,我才会感到不舒服:-))。当然,我也认为性能是重中之重,如果你能得到相同的结果,就必须牺牲视图结构。继续下一条评论->
  • 现在我会因此而直接寻求您的解决方案,但当我想到它时,我发现了该方法的最后一个问题:您在屏幕上看到的“聊天”项目将被拆分为不同的独立 ViewHolder,但我的一个用例是整个“聊天”项目必须可以向左或向右滑动。因此我正在使用 ItemTouchHelper.Callback 但如果我将它与您的方法一起使用,每个“聊天消息视图”都会自行滑动,我不知道如何使 6 项聊天项全部移动当我滑动时。继续下一条评论->
  • 请不要误解我的意思,我真的很喜欢你的方法,我很确定它是最好的。我只是提出其他仍然存在的问题。
  • 嗯,我明白了。圆角之类的东西只会增加 acceptable 复杂性(imo),使构造可滑动似乎确实是一个问题。再次阅读原始问题,我想我错误地认为内部回收器视图不能滚动,是吗?
【解决方案2】:

很多cmets和相关问题说两个垂直嵌套的RecyclerViews在android中是不可能的

这不是真的;谁说这不是 事情 并没有做到这一点,并认为这是不可能的。有可能,尽管有复杂性、副作用,而且很可能是用户在尝试向上/向下点击时感到烦恼,而 错误 触摸拦截器获胜。

为什么会出现这个问题?

在 iOS 上,当你尝试做一些平台开发者认为不好的事情时,大多数人和其他开发者对你尖叫不要与框架作斗争!!!。 在 Android 上,我们看到了最疯狂的 Java(现在是 Kotlin)实现,这让您想知道 我们 - 开发人员 - 在学校学习什么以及我们在教什么?! 但没有人说什么(因为大部分):p

事实是,您正在尝试设计复杂的用户交互和数据转换,但是,您的尝试偏向于“按原样”使用数据(这意味着处理这两个不同的 RV/适配器),而不是做一个应该做的事情:转换数据以进行展示。

这就引出了下一个问题:

你将如何实现这样的结构。

嗯,首先,我不知道您的数据是什么样的,也不知道它来自哪里;除了明显的滚动之外,我不知道您的用户可以对您的数据做什么。

除了您的模型之外,我也不知道您的数据希望如何呈现。

但我确实非常了解情况。事物列表,其中也包含自己的事物列表。

案例:列表列表

这是可行的;你可以有一个列表,在所说的列表中,有另一个列表。我已经做到了。我见过别人做的。我用过。我也从来不喜欢有这个“小”可滚动的东西,当我点击“错误的地方”时,我会争先恐后地看谁先滚动。

我不会这样做。如果内部列表很大(例如每个外部项目超过 3 个项目),我不会将其呈现为可滚动内容。

会做什么(考虑到我所做的事情知道您的问题)是有一个列表显示所有内容适当地压平。

这与您的内容有问题:

如果内部列表超长怎么办,这不会导致它们全部显示吗?是的,这就是为什么如果数据(如您所描述的)可以包含 100 个项目,我不会这样做。一个选项是显示带有“更多”链接的前 3 个项目,以现在打开内部列表“全屏”;这比用户 PoV 和技术方面的嵌套列表好 10 倍。

另一种选择是保留这个单一的长列表 (RV-1),并让用户“扩展”列表以在单独的窗口中启动另一个描述 RV-2 内容的全屏列表。这样就更好了。

您将花费时间来实现这一点并摆脱您现在可能拥有的混乱代码,这会让您想知道为什么您一开始不建议这样做。

如果这是您绝对无法做到的事情,那么我无法为您提供更多建议,因为现在您与我不知道的业务/产品规则相关联。最终,当您的应用程序的用户不得不滚动那个噩梦时,他们将为此付出代价:)

退后一步

让我说清楚,我不是在批评你或你的解决方案;我只是指出,根据我的经验,您在这里拥有的这种“模式”并不是一个好的用户体验。

  • 格式化您的数据以进行演示,而不是相反。您的数据应该被正确地塑造,以便可以使用您拥有的工具正确呈现。
  • 您正在与 Android 提供的工具作斗争;当 RecyclerView(及其适配器)已经有很多 很多 发生时,你给它提供了很多新问题要处理。

想一想:RecyclerViews 要做很多事情;适配器还必须符合一些接口,确保尽快分派,计算差异(如果使用ListAdapter<T,V>)等等。活动/片段?他们有很多事情要处理......好吧“Android”;现在您要求所有这些组件也处理滚动内容、触摸识别、事件处理、视图膨胀等复杂场景。 所有这一切,虽然期望每个视图花费 16 毫秒或更短的时间(为了保持 60 FPS 以上的滚动速度,您的视图/viewHolder 不应该花费超过 16 毫秒来完成所有它需要的

相反,我要求您退后一步,获取您拥有的数据,对其进行组合、转换、映射,并创建可以更好地为您拥有的组件服务的数据结构(RV + 适配器 +一个简单的视图)。

祝你好运:)

【讨论】:

  • 嘿,首先非常感谢您的深入解释。我知道我没有提供很多关于我的商业案例的信息等等。如果您能看看我的 EDIT 2 和 EDIT 3,我会非常高兴,因为它们更深入地了解了这个应用程序的外观和行为方式。帮助您稍微了解一下:整个事情是一种聊天应用程序,在他们的聊天中拥有“更多...”按钮并不是很好的体验,但非常感谢您的建议。也许您在阅读我的编辑后会更好地理解为什么我必须选择这个特定的结构。
猜你喜欢
  • 2017-06-21
  • 1970-01-01
  • 1970-01-01
  • 2016-12-09
  • 1970-01-01
  • 1970-01-01
  • 2020-07-05
  • 1970-01-01
  • 2020-03-28
相关资源
最近更新 更多