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

时间:2018-04-25 10:26:23

标签: android android-recyclerview kotlin rx-kotlin

我的外部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。此布局用于显示消息和 内部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
      }
    }

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

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)
}

过去几天我一直在讨论这个问题,但我无法理解它。非常感谢任何方向的任何帮助。我知道我的解释可以在所有地方进行,所以只需要在需要时澄清:)

更新

我现在能够使用GridLayout以及RecyclerView生成此内容。可以安全地假设嵌套的RecyclerViews不是这里的罪魁祸首。我甚至试图抛弃处理加载图像的Rx-piece并为该过程创建IntentService,但仍然会发生相同的崩溃。

使用GridLayout我的意思是,我没有使用另一个适配器来填充嵌套的RecyclerView,而是使用一个适配器来填充消息,并为附件充气和填充视图并附加这些嵌套GridLayout的视图。

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

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

  1. 打开包含附件邮件的讨论
  2. 开始向上滚动以查看更多消息
  3. 传递带附件的邮件,并在其仍在加载
  4. 时将其滚出屏幕
  5. 崩溃
  6. 如果我停下来等待下载完成并且一切按预期工作,则不会发生崩溃。使用适当的缩略图更新图像/视频/文件,如果我将其滚动到视图外,应用程序将不会崩溃。

    更新2

    我尝试将嵌套的ViewGroup换成单个ImageView,只是为了查看嵌套中的问题。瞧,看哪!它仍然崩溃。现在我真的很困惑,因为我之前提到过的DiscussionListAdapter在其中具有相同的确切内容并且其中一个像魅力一样......我的搜索仍在继续。我希望某人有一天能从我的痛苦中受益。

    更新3

    我开始记录ViewHolder函数中每个onBindViewHolder()的父级。与预期一样,我在nullsnulls获得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作为它的父母?父是一个不可变的值,因此ViewOverlay被设置为父的唯一方法是构造一个新的ViewHolder并提供ViewOverlay作为父对象。

    更新4

    有时候我会因为自己的愚蠢而惊讶自己。使用ViewOverlay是因为项目正在动画化。我甚至不认为这是一个选项,因为我已将itemAnimator的{​​{1}}设置为RecyclerView,但由于某些奇怪的原因无效。这些物品仍然是动画的,这引起了整个游戏。那可能是什么原因呢? (我怎么选择忽略移动的项目,我不知道,但是当我强迫应用程序一遍又一遍地下载相同的图片并且整个列表变得混乱时,动画变得非常清晰。) < / p>

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

    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,但动画不会停止!我已经尝试在根nullanimateLayoutChanges上设置ConstraintLayout xml属性,但这些属性都不起作用。值得一提的是,我还检查了RecyclerView在程序的不同状态下是否有RecyclerView,每次检查动画师时都是空的。那么什么是动画我的itemAnimator?!

2 个答案:

答案 0 :(得分:0)

我遇到了同样的问题

在您的孩子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);

希望这会有所帮助

答案 1 :(得分:0)

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