老铁们,之前在开发过程中我遇到一个困惑,是关于 Fragment.onCreateView 中怎样 inflate 一个 View 的问题,但是后来忘记了,最近又遇到了这个问题,备忘一下,有遇到同样问题的小伙伴们可以做一个参考
在某些场合比如 Fragment.onCreateView 中需要返回一个 View,那我们就从 xml 中 inflate 一个吧,inflate 方法需要两个参数,那就给吧
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank, container)
}
结果就悲剧了,inflate 虽然成功了,但是会崩
2022-02-14 11:47:27.322 25073-25073/com.kiyuni.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.kiyuni.myapplication, PID: 25073
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
at android.view.ViewGroup.addViewInner(ViewGroup.java:5247)
at android.view.ViewGroup.addView(ViewGroup.java:5076)
at android.view.ViewGroup.addView(ViewGroup.java:5016)
at androidx.fragment.app.FragmentStateManager.addViewToContainer(FragmentStateManager.java:833)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:523)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:282)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2100)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3138)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:3072)
at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:251)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:502)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:246)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1455)
at android.app.Activity.performStart(Activity.java:8076)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3660)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7838)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
为什么会崩呢,因为 inflate 内部是这样实现的
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
因为 root 不是 null,那么,最后一个参数 attachToRoot == true,那么就会把 inflate 的结果直接 add 到 root 上,那 Fragment 发现你返回的 View 已经被 Add 一次了(虽然是我们好心帮他add上去的),它就不干了,因为一个 View 被 add 多次肯定发生问题,View 树就不是一棵树了
那好吧,我们把 container 换成 null 试试,结果 lint 又不干了,警告我
Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element)
它说,它需要 root 去解析 layout parameters,那我们就明白了,在学习 View 的原理的时候我们知道,子 View 的一些属性,比如大小,是需要和父 View 一起协商确定的,你不咨询父View的宽度,你的要求就被忽略了,会导致一些奇怪的Bug的,比如你写的 layout_width 参数失效,这方面小编真的曾经被困惑到,怎么别人的 Fragment View 就能 match parent 我的就不行呢,后来才发现是这个原因,所以正确的方法是,提供 root,但是不要 attach
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank, container, false)
}
如果头铁就要把parent传null,倒也不是不行,可以手动把layout参数用java代码再补上,像下面这个
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_bypass_advance_test_case, null)
parent.addView(view)
// 此时把view布局中根布局声明的layout_*参数补上,此时view的layoutParams中的属性就手动加上了
// 注意layoutParams的类型是parent对应的,如果需要可以强转
view.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
view.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
parent.removeView(view)
return ViewHolder(view)
}
这不是浪摧的嘛,费这劲。。。
你学废了吗?
lint的警告有时候是 bug 的前兆,不要轻视它,对于任何的警告,除非有百分百把握,小编我都是要逐个解决的。