DialogFragment嵌套FragmentContainerView报错,如何保留子Fragment复用性?
问题
我有一个代表自定义数字键盘的NumpadFragment,希望将其放入Dialog弹窗中。为此编写了如下布局(dialog_numpad.xml):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.fragment.app.FragmentContainerView android:id="@+id/fragmentContainerView" android:name="com.github.me.myapp.NumpadFragment" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> </LinearLayout>
同时编写了如下Dialog类:
public class NumpadPopupFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); LayoutInflater inflater = requireActivity().getLayoutInflater(); View view = inflater.inflate(R.layout.dialog_numpad, null); builder .setView(view) .setPositiveButton("Start", null) .setNegativeButton("Cancel", null); return builder.create(); } }
但从Activity中通过以下代码实例化弹窗时:
NumpadPopupFragment dialog = new NumpadPopupFragment(); dialog.show(getSupportFragmentManager(), "new");
出现如下致命异常:
E/AndroidRuntime: FATAL EXCEPTION: main Process: com.github.me.myapp PID: 21457 android.view.InflateException: Binary XML file line #6 in com.github.me.myapp:layout/dialog_numpad: Binary XML file line #6 in com.github.me.myapp:layout/dialog_numpad: Error inflating class androidx.fragment.app.FragmentContainerView Caused by: android.view.InflateException: Binary XML file line #6 in com.github.me.myapp:layout/dialog_numpad: Error inflating class androidx.fragment.app.FragmentContainerView Caused by: java.lang.IllegalStateException: FragmentManager is already executing transactions
我猜测是不允许嵌套Fragment,但查阅资料得知Android自2011年起支持嵌套Fragment。请问是否有实现需求的方法?出于架构考虑,我不想将子键盘Fragment合并到DialogFragment中,希望保留其在弹窗之外的复用性。
解决方案
这个错误的核心原因是:在onCreateDialog方法中直接加载包含FragmentContainerView的布局时,DialogFragment自身的事务还在执行流程中,嵌套的FragmentContainerView尝试触发Fragment事务就会冲突,导致FragmentManager is already executing transactions异常。
以下两种方法可以解决问题,同时保留NumpadFragment的复用性:
方法一:延迟加载嵌套Fragment
- 修改
dialog_numpad.xml,将FragmentContainerView替换为普通FrameLayout作为容器:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@+id/numpad_container" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> </LinearLayout>
- 修改
NumpadPopupFragment,在onViewCreated方法中手动添加NumpadFragment(此时DialogFragment的初始化事务已完成):
public class NumpadPopupFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); LayoutInflater inflater = requireActivity().getLayoutInflater(); View view = inflater.inflate(R.layout.dialog_numpad, null); builder .setView(view) .setPositiveButton("Start", null) .setNegativeButton("Cancel", null); return builder.create(); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // 使用getChildFragmentManager()加载嵌套Fragment,这是嵌套Fragment的核心要求 getChildFragmentManager().beginTransaction() .replace(R.id.numpad_container, NumpadFragment.class, null) .commit(); } }
方法二:使用DialogFragment的onCreateView替代onCreateDialog
放弃AlertDialog.Builder,直接在onCreateView中返回包含FragmentContainerView的布局,系统会自动处理嵌套Fragment的事务时机:
- 保留原
dialog_numpad.xml不变,修改NumpadPopupFragment:
public class NumpadPopupFragment extends DialogFragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // 加载布局,此时FragmentContainerView会自动使用getChildFragmentManager()初始化嵌套Fragment return inflater.inflate(R.layout.dialog_numpad, container, false); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // 若需要保留按钮,可在布局中添加按钮栏并在这里设置点击事件 view.findViewById(R.id.cancel_btn).setOnClickListener(v -> dismiss()); view.findViewById(R.id.start_btn).setOnClickListener(v -> { // 处理Start按钮逻辑 dismiss(); }); } }
- 如需保留"Start"和"Cancel"按钮,修改
dialog_numpad.xml添加按钮栏:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <androidx.fragment.app.FragmentContainerView android:id="@+id/fragmentContainerView" android:name="com.github.me.myapp.NumpadFragment" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="16dp"> <Button android:id="@+id/cancel_btn" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Cancel" /> <Button android:id="@+id/start_btn" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Start" /> </LinearLayout> </LinearLayout>
内容的提问来源于stack exchange,提问作者Jack M
相关产品推荐
相关产品推荐

