将 RecyclerViews itemAnimator 设置为 null 不会删除动画

Setting RecyclerViews itemAnimator to null does not remove animations

我的外部 RecyclerView

而崩溃
IllegalArgumentException: Scrapped or attached views may not be recycled. isScrap:false isAttached:true...

IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

如标题所示,我在第一个 RecyclerView 的列表项布局中有一个 RecyclerView。此布局用于显示消息和 inner RecyclerView 显示邮件附带的附件。内部 RecyclerViews 可见性设置为 GONEVISIBLE,具体取决于消息是否有任何附件。简化的外部列表项布局如下所示

ConstraintLayout
    TextView
    TextView
    TextView
    RecyclerView

处理内部 RecyclerView 的适配器部分看起来像这样

private fun bindFiles(message: Message?) = with(itemView) {
      if (message != null && message.attachments.isNotEmpty())
      {
            sent_message_attachments.setAsVisible()
            sent_message_attachments.layoutManager = GridLayoutManager(this.context,Math.min(message.attachments.size,3))
            sent_message_attachments.adapter = AttachmentAdapter(message.attachments)
            sent_message_attachments.itemAnimator = null
            sent_message_attachments.setHasFixedSize(true)
      }
      else{
            sent_message_attachments.setAsGone()
            sent_message_attachments.adapter = null
            sent_message_attachments.layoutManager = null
      }
    }

这个错误与我在内部适配器中获取附件的方式有关,因为一旦我禁用启动下载过程的部分,一切都很好。从设备加载图像时没有问题,但是一旦我开始下载过程,一切都会变得糟糕。这是处理图像并在内部适配器中启动下载过程的部分。我有视频和其他文件类型的功能,它们几乎完全相同,但布局略有不同。

private fun bindImage(item: HFile?) = with(itemView) {
      if (item != null)
      {
        if (item.isOnDevice && !item.path.isNullOrEmpty())
        {
          if (item.isGif)
          {
            attachment_image.displayGif(File(item.path))
          }
          else
          {
            attachment_image.displayImage(File(item.path))
          }
        }
        else
        {
          //TODO: Add option to load images manually
          FileHandler(item.id).downloadFileAsObservable(false)
              .subscribeOn(Schedulers.io())
              .observeOn(AndroidSchedulers.mainThread())
              .subscribe(
                  { progress ->
                    //TODO: Show download process
                  },
                  { error -> 
                  error.printStackTrace()
                  //TODO: Enable manual retry 
                  },
                  { notifyItemChanged(adapterPosition)} //onComplete
              )
        }
      }
  }

我在我的 DiscussionListAdapter 中使用与上面相同的结构来加载讨论肖像(个人资料图片等)并且它没有相同的问题。

这些是用于膨胀 viewHolders 和显示图像的扩展函数

fun ViewGroup.inflate(layoutRes: Int): View
{
  return LayoutInflater.from(context).inflate(layoutRes, this, false)
}

fun ImageView.displayGif(file:File){
  GlideApp.with(context).asGif().load(file).transforms(CenterCrop(), RoundedCorners(30)).into(this)
}

fun ImageView.displayImage(file:File){
  GlideApp.with(context).load(file).transforms(CenterCrop(), RoundedCorners(30)).into(this)
}

过去几天我一直在研究这个问题,只是无法理解它。非常感谢任何方向的帮助。我知道我的解释可能有点到处都是,所以只需要在需要时要求澄清:)

更新

我现在已经能够用 GridLayoutRecyclerView 制作这个了。可以安全地假设嵌套 RecyclerViews 不是这里的罪魁祸首。我什至尝试放弃处理加载图像的 Rx-piece 并为该过程创建 IntentService,但同样的崩溃仍然发生。

With GridLayout 我的意思是我没有使用另一个适配器来填充嵌套的 RecyclerView 我只使用一个适配器来填充消息并为附件扩充和填充视图以及将这些视图附加到嵌套的 GridLayout.

当我开始下载文件然后将视图(本应显示下载的文件)滚动到屏幕外时,发生了崩溃。该视图应该得到回收,但由于某种原因,下载过程(在我的测试用例中只需要大约 100ms-400ms)导致应用程序抛出原始问题中提到的两个错误之一。可能值得注意的是,我正在使用 Realm 并且适配器将 RealmResults<Message> 列表作为数据集。我的演示者在列表中查找更改,然后在需要时通知适配器(由于 IntentService 的实施而更改)。

这就是我能够一次又一次重现的方式:

  1. 打开包含带附件的消息的讨论
  2. 开始向上滚动查看更多消息
  3. 传递带有附件的消息,并在它仍在加载时将其滚动到屏幕外
  4. 崩溃

如果我停下来等待下载完成并且一切正常,则不会出现崩溃。 image/video/file 会更新为正确的缩略图,如果我将其滚动到视图之外,应用程序也不会崩溃。

更新 2

我尝试将嵌套 ViewGroup 换成单个 ImageView 只是为了查看嵌套中的问题。瞧!它仍然崩溃。现在我真的很困惑,因为我之前提到的 DiscussionListAdapter 里面有完全相同的东西,而且它就像一个魅力......我的搜索继续。我希望有一天有人能从我的痛苦中受益。

更新 3

我开始记录 onBindViewHolder() 函数中每个 ViewHolder 的 parent。正如预期的那样,我在 nulls 之后 nulls 之后得到了 nulls,然后应用程序崩溃并吐出它。

04-26 21:54:50.718 27075-27075/com.hailer.hailer.dev D/MsgAdapter: Parent of ViewHolder: android.view.ViewOverlay$OverlayViewGroup{82a9fbc V.E...... .......D 0,0-1440,2168}

毕竟我的疯狂是有办法的!但这只会提出更多问题。为什么这里使用ViewOverlay?作为 RecyclerView 的一部分或作为黑暗魔法师的一部分计划剥夺我的理智?

旁注

我深入研究了 RecyclerViews 代码来检查我是否能找到 ViewOverlay 谜团的原因。我发现 RecyclerView 只调用适配器 onCreateViewHolder() 函数两次。两次都将自己提供为函数的 parent 参数。所以运气不好...到底是什么导致项目视图具有 ViewOverlay 而不是 parent? parent 是一个不可变的值,因此将 ViewOverlay 设置为 parent 的唯一方法是构造一个新的 ViewHolder 并提供 ViewOverlay 作为 parent object.

更新 4

有时我为自己的愚蠢感到惊讶。使用 ViewOverlay 是因为项目正在动画化。我什至不认为这是一个选项,因为我已将 RecyclerViewitemAnimator 设置为 null,但由于某些奇怪的原因不起作用。这些项目仍在动画中d 这导致了整个游戏。那么这可能是什么原因呢? (我是如何选择忽略移动的项目的,我不知道,但是当我强迫应用程序一遍又一遍地下载相同的图片并且整个列表变得混乱时,动画变得非常清晰。)

我的 DiscussionInstanceFragment 包含有问题的 RecyclerView 和一个嵌套的 ConstraintLayout,后者又包含一个 EditText 用于用户输入和一个发送按钮。

    val v = inflater.inflate(R.layout.fragment_discussion_instance, container, false)
    val lm = LinearLayoutManager(context)
    lm.reverseLayout = true
    v.disc_instance_messages_list.layoutManager = lm
    v.disc_instance_messages_list.itemAnimator = null
    v.disc_instance_messages_list.adapter = mPresenter.messageAdapter

这是处理 RecyclerView 初始化的部分。我绝对会将 itemAnimator 设置为 null,但动画不会停止!我试过在根 ConstraintLayoutRecyclerView 上设置 animateLayoutChanges xml 属性,但它们都不起作用。值得一提的是,我在程序的不同状态下也检查了RecyclerView是否有一个itemAnimator,每次检查animator都是null。那么是什么让我的 RecyclerView 动起来了?!

我遇到了同样的问题

在你的 child 中试试这个 RecyclerView 它对我有用

RecyclerView childRC = itemView.findViewById(R.id.cmol_childRC);
layoutManager = new LinearLayoutManager(context);
childRC.setItemAnimator(null);
childRC.setLayoutManager(layoutManager);
childRC.setNestedScrollingEnabled(false);
childRC.setHasFixedSize(true);

现在像这样设置你的Adapter

ArrayList<Model> childArryList = new ArrayList<>();
childArryList.addAll(arrayList.get(position).getArrayList());
ChildOrderAdapter adapter = new ChildOrderAdapter(context, childArryList);
holder.childRC.swapAdapter(adapter, true);

希望这对您有所帮助

我终于弄清楚是什么原因造成的。在我的 DiscussionInstanceView 中,我有一个小的 view,它使用 ConstraintLayout 关键帧动画进行动画处理。此视图仅显示聊天记录的下载进度,并且仅在首次打开讨论时使用一次。但是由于每次我的数据集更新时我都会调用隐藏该视图,所以我强制 ConstraintLayout 触发动画序列,从而使数据集更新期间的所有内容都具有动画效果。我只是添加了一个简单的检查我是否在下载历史记录,这个问题得到了解决。