使用addView()时View.resetResolvedLayoutDirection致应用崩溃问题
问题分析与解决方案
问题根源
你的代码触发StackOverflowError的核心原因有两个:
- 循环嵌套导致无限递归:你没有排除自定义View内部的根布局(即你inflate的包含topSection和collapsableSection的ConstraintLayout),遍历所有子View时会把这个根布局也移到collapsableSection里。而collapsableSection本身是根布局的子View,这就形成了「根布局 → collapsableSection → 根布局」的循环嵌套,布局系统处理方向属性时会无限递归调用
resetResolvedLayoutDirection,直到栈溢出。 - 遍历逻辑缺陷:从0到
childCount正向遍历,每次removeView后子View索引会前移,导致部分用户添加的子View被跳过,无法正确移入折叠区域。
修复后的代码实现
第一步:初始化时记录内部根布局
在自定义View的初始化逻辑中,先加载内部布局并记录根布局,避免后续误操作内部组件:
private lateinit var rootLayout: ConstraintLayout private lateinit var collapsableSection: LinearLayout init { val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater // 加载自定义View的内部布局 rootLayout = inflater.inflate(R.layout.your_custom_view_layout, this, false) as ConstraintLayout collapsableSection = rootLayout.findViewById(R.id.collapsableSection) // 先把内部根布局添加到自定义View中 addView(rootLayout) }
第二步:修正子View移动逻辑
先收集所有用户添加的子View(排除内部根布局),再批量移动,避免遍历过程中布局结构变化导致的问题:
fun addChildrenToCollapsableSection() { // 收集所有用户在XML中添加的子View(排除内部根布局) val userChildren = mutableListOf<View>() for (i in 0 until childCount) { val child = getChildAt(i) if (child != rootLayout) { userChildren.add(child) } } // 批量移动子View到折叠区域 userChildren.forEach { child -> removeView(child) // 设置合适的布局参数,保证子View正常显示 collapsableSection.addView(child, ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT )) } }
第三步:在合适时机触发移动
重写onFinishInflate方法,确保所有XML中的子View都加载完成后再执行移动操作:
override fun onFinishInflate() { super.onFinishInflate() addChildrenToCollapsableSection() }
额外注意事项
- 布局参数:添加子View到collapsableSection时,必须设置匹配父容器的布局参数,否则可能出现子View宽度不足、显示异常的问题。
- 避免重复调用:如果你的自定义View支持动态添加子View,需要确保
addChildrenToCollapsableSection不会重复执行,否则会导致子View被多次移动。
内容的提问来源于stack exchange,提问作者Kael Nevarez Lara
相关产品推荐
相关产品推荐

