【发布时间】:2018-04-25 10:26:22
【问题描述】:
我的外部 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 可见性设置为GONE 或VISIBLE,具体取决于邮件是否包含任何附件。简化的外部列表项布局如下所示
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)
}
过去几天我一直在研究这个问题,但我无法理解它。非常感谢任何方向的任何帮助。我知道我的解释可能有点笼统,所以只要在需要时要求澄清:)
更新
我现在已经能够使用GridLayout 和RecyclerView 来生成它。可以安全地假设嵌套的RecyclerViews 不是这里的罪魁祸首。我什至试图放弃处理加载图像的 Rx-piece 并为该过程创建了一个 IntentService,但仍然会发生同样的崩溃。
对于GridLayout,我的意思是,我没有使用另一个适配器来填充嵌套的RecyclerView,而是只使用一个适配器来填充消息,并膨胀和填充附件的视图,并将这些视图附加到嵌套的GridLayout.
当我开始下载文件然后将视图(应该显示下载的文件)滚动到屏幕外时,就会发生崩溃。该视图应该被回收,但由于某种原因,下载过程(在我的测试用例中只需要大约 100ms-400ms)导致应用程序抛出原始问题中提到的两个错误之一。可能值得注意的是,我正在使用Realm,并且适配器将RealmResults<Message> 列表作为它的数据集。我的演示者在列表中查找更改,然后在需要时通知适配器(由于IntentService 的实现而更改)。
这就是我能够一次又一次地重现这种情况的方式:
- 打开包含带有附件的消息的讨论
- 开始向上滚动查看更多消息
- 传递带有附件的消息并在它仍在加载时将其滚动到屏幕外
- 崩溃
如果我停止并等待下载完成并且一切都按预期工作,则不会发生崩溃。图像/视频/文件会使用正确的缩略图进行更新,如果我将其滚动到视野之外,应用程序不会崩溃。
更新 2
我尝试将嵌套的 ViewGroup 替换为单个 ImageView 只是为了查看嵌套中的问题。瞧!它仍然崩溃。现在我真的很困惑,因为我之前提到的DiscussionListAdapter 有同样的东西,而且它就像一个魅力......我的搜索还在继续。我希望有一天有人能从我的痛苦中受益。
更新 3
我开始在onBindViewHolder() 函数中记录每个ViewHolder 的父级。正如预期的那样,我在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 代码以检查是否可以找到ViewOverlaymystery 的原因。我发现RecyclerView 只调用了适配器onCreateViewHolder() 函数两次。两次都将自己作为函数的 parent 参数提供。所以没有运气......到底是什么导致项目视图将ViewOverlay作为它的父项?父对象是一个不可变的值,因此将ViewOverlay 设置为父对象的唯一方法是构造一个新的ViewHolder 并提供ViewOverlay 作为父对象。
更新 4
有时我会惊讶于自己的愚蠢。使用 ViewOverlay 是因为项目正在被动画化。我什至不认为这是一个选项,因为我已将 RecyclerView 的 itemAnimator 设置为 null,但由于某些奇怪的原因,它不起作用。这些项目仍在动画中,这导致了整个游戏。那么这可能是什么原因呢? (我不知道我是如何选择忽略移动项目的,但是当我强迫应用一遍又一遍地下载同一张图片时,动画变得非常清晰,整个列表都变得混乱了。)
我的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,但动画不会停止!我尝试在根ConstraintLayout 和RecyclerView 上设置animateLayoutChanges xml 属性,但它们都不起作用。值得一提的是,我还检查了RecyclerView在程序的不同状态下是否有itemAnimator,每次检查动画师,都是空的。那么是什么在为我的RecyclerView 设置动画?!
【问题讨论】:
标签: android android-recyclerview kotlin rx-kotlin