在DialogFragment中使用ViewPager - IllegalStateException: Fragment does not have a view

34

我想要达成的目标

  • 从一个FragmentActivity中,在点击操作栏中的操作按钮时显示一个对话框
  • DialogFragment - 无标题的对话框
  • TabHost - 对话框顶部的选项卡
  • FragmentPagerAdapterViewPager - 可滑动,其内容与选项卡相连接
  • 2-3个对话框按钮(对话框的不同子类,不同的按钮) - 不应该位于ViewPagerFragment之一,也就是说,无论ViewPager显示哪个Fragment,这些按钮都应该保留在对话框底部。


问题

IllegalStateException: Fragment does not have a view


我已经尝试/完成的部分

  • 使用android.support.v4包中必要的类
  • 调用getChildFragmentManager()而不是getSupportedFragmentManager()
  • 实现了此链接中推荐的第10个帖子https://code.google.com/p/android/issues/detail?id=42601的内容。 我将代码直接复制/粘贴到我的两个Fragment类以及DialogFragment类中,这些类应该显示ViewPager
  • 在我的自定义DialogFragment中,我首先尝试重写onCreateView,然后是onCreateDialog,最后同时进行两者。所有这些我都得以运行,但结果却出乎意料。
    • 只有onCreateView:无法访问AlertDialog.Builder以创建所需的按钮,除此之外,对话框的结果非常好。
    • onCreateDialog:上面显示的错误消息。我仍然认为这种方法是我接近想要实现的内容的方法。
    • 同时进行onCreateViewonCreateDialog:在onCreateView中填充了对话框布局,并在onCreateDialog中将对话框按钮添加到AlertDialog.Builder中。这可以显示对话框,但是来自AlertDialog.Builder的添加按钮不可见。此外,当单击EditText字段时键盘没有出现。


源代码

大部分来自Android 2.2+ViewPager和Fragments中实现TabHost使用的教程。其中ActivityFragment的代码位于DialogFragment中。然而,我用答案来源于https://dev59.com/K2oy5IYBdhLWcg3wq_7O#18167273的已修改的ViewPager替换了它的ViewPager。 这是为了能够使高度wrap_content

我项目中的有问题的代码应该在DialogFragmentonCreateDialog方法中。

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), AlertDialog.THEME_HOLO_DARK);
    LayoutInflater inflater = getActivity().getLayoutInflater();
    view = inflater.inflate(R.layout.dialog_test, null);
    addActionButtons(builder, view);    
    builder.setView(view);

    mViewPager = (WrapContentHeightViewPager) view.findViewById(R.id.viewpager);

    initialiseTabHost();

    List<Fragment> fragments = getFragments();

    pageAdapter = new DialogPageAdapter(getChildFragmentManager(), fragments);
    mViewPager.setAdapter(pageAdapter);
    mViewPager.setOnPageChangeListener(this);

    Dialog dialog = builder.create();
    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
    dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
    dialog.show();

    return dialog;
}

堆栈跟踪LogCat日志

FATAL EXCEPTION: main
java.lang.IllegalStateException: Fragment does not have a view
    at android.support.v4.app.Fragment$1.findViewById(Fragment.java:1425)
    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:901)
    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1088)
    at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:682)
    at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1444)
    at android.support.v4.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.java:461)
    at android.support.v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:141)
    at android.support.v4.view.ViewPager.populate(ViewPager.java:1011)
    at android.support.v4.view.ViewPager.populate(ViewPager.java:880)
    at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1374)
    at my.app.package.name.WrapContentHeightViewPager.onMeasure(WrapContentHeightViewPager.java:31)
    at android.view.View.measure(View.java:15481)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
    at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1396)
    at android.widget.LinearLayout.measureVertical(LinearLayout.java:681)
    at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
    at android.view.View.measure(View.java:15481)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
    at android.view.View.measure(View.java:15481)
    at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:617)
    at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:399)
    at android.view.View.measure(View.java:15481)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
    at android.view.View.measure(View.java:15481)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
    at android.view.View.measure(View.java:15481)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
    at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1396)
    at android.widget.LinearLayout.measureVertical(LinearLayout.java:681)
    at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
    at android.view.View.measure(View.java:15481)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
    at android.view.View.measure(View.java:15481)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
    at android.view.View.measure(View.java:15481)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
    at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2377)
    at android.view.View.measure(View.java:15481)
    at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1982)
    at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1200)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1398)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1118)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4525)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
    at android.view.Choreographer.doCallbacks(Choreographer.java:555)
    at android.view.Choreographer.doFrame(Choreographer.java:525)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
    at android.os.Handler.handleCallback(Handler.java:615)
    at android.os.Handler.dispatchMessage(Handler.java:92)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:4946)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.jav


此外...

  • 我没有成功地尝试过在https://code.google.com/p/android/issues/detail?id=42601中提到的其他可能解决方案,这在帖子#2和#13中提到,因为我没有理解我在哪里和如何在我的代码中使用它(我猜我和写#18的人处于同一艘船上)。

如果你成功实现了,请分享你的代码。 - shripal
6个回答

38

我觉得我遇到了同样的问题,并通过查看DialogFragment的源代码学到了一些东西。

看起来,即使覆盖onCreateDialog(...)是创建自定义对话框的有效方法,但它将导致DialogFragment具有null View,就像错误消息所说的那样。在大多数情况下,这没关系 - DialogFragment不需要View来显示对话框,但是如果您想进一步嵌套片段(就像您所做的那样),这是不可行的。

考虑到您想要与AlertDialog.Builder交互,我真的看不到完美的解决方案,但您有几个选项:

  1. 将对话框中的按钮作为视图的一部分创建(而不是使用AlertDialog.Builder)。您可以通过覆盖onCreateView而不是onCreateDialog来实现此操作。通过将按钮放在它们自己的片段中,您应该能够获得所需的功能。我们在我的演出中做了类似的事情,我非常喜欢这种方法。
  2. 实现自己的类型,该类型继承自Fragment,并以每种方式镜像DialogFragment,除了允许您所需的内容。由于DialogFragment仅为~400,而且有很多注释,因此这应该不会太可怕。可能会很有趣。
  3. 使用常规PagerAdapter而不是FragmentPagerAdapter。这样,您的DialogFragment没有View也无所谓。

22

使用

onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)

使用提供的inflater而不是onCreateDialog(Bundle savedInstanceState)来创建警告对话框,并构建视图。这对我有效。

最好的问候!


这真的奏效了。如果你像我一样要覆盖onCreateDialog来创建对话框,这会让你省心许多。 - Anfet
1
我遇到了与BottomSheetDialogFragment相同的问题,但是当我尝试在底部工作表本身上添加一个片段时,使用此方法而不是setupDialog(Dialog dialog, int style)方法解决了问题。 - Clive Jefferies

9
如果您实现了onCreateDialog 来使用 AlertDialog,并且在访问getChildFragmentManager或类似内容时遇到IllegalStateException:Fragment does not have a view的错误。
为了解决这个问题,需要同时实现onCreateDialogonCreateView,其中onCreateView返回onCreateDialog中充气的视图。
class LocationPickerDialog : DialogFragment() {

    lateinit var customView: View

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return customView
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        Log.d(TAG, "onCreateDialog")
        // StackOverflowError
        // customView = layoutInflater.inflate(R.layout.dialog_location_picker, null)
        customView = activity!!.layoutInflater.inflate(R.layout.dialog_location_picker, null)

        val builder = AlertDialog.Builder(context!!)
                .setView(customView)
                .setPositiveButton(android.R.string.ok) { _, _ ->
                    // do something
                }
                .setNegativeButton(android.R.string.cancel) { _, _ ->
                    // do something
                }
        val dialog = builder.create()

        return dialog
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        // if onCreateView doesn't return a view 
        // java.lang.IllegalStateException: Fragment does not have a view
        mapFragment = childFragmentManager.findFragmentByTag("map") as SupportMapFragment?
    }
}

https://code.luasoftware.com/tutorials/android/android-alertdialog-in-dialogfragment-fragment-does-not-have-a-view/


谢谢。您的建议使我能够处理一个困难的角落案例 - https://dev59.com/bLLma4cB1Zd3GeqPaGML - Cheok Yan Cheng

2
感谢@Tommy Visic编写了一份非常好的说明,它起到了作用。
以下是对我有效的代码。
我已经从onCreateDialog方法中删除了Dialog构建代码,实际上删除了onCreateDialog方法,并且在Dialog的自定义视图中实现的对话框视图已被包含为View对象,在onCreateView方法中,所有这些事情都开始工作了。
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.login_sigup_screen, null, false);
    bind = ButterKnife.bind(this, view);
    initViewPager();
    return view;
}

这个实现还面临一个问题:
当一个Activity有Toolbar/ActionBar时,它也会显示在DialogFragment中,为了避免这种情况,需要执行以下操作: 实现Fragment的onViewCreated方法并添加以下代码:
Dialog dialog = getDialog();
dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
float dimAmount = 0.6f;
dialog.getWindow().setDimAmount(dimAmount);

这将从 DialogFragment 中移除 Toolbar,并且以原样显示 Activity

祝好

感谢 @Tommy

敬礼 Zeus


0

我遇到了同样的问题,并实现了以下解决方案:

class MyDialogFragment: DialogFragment {

  private lateinit var dialog: AlertDialog
  private lateinit var dialogView: View

  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    // return the view inflated for your dialog fragment
    return dialogView
  }

  // this is called before onCreateView
  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    dialogView = LayoutInflater
            .from(ContextThemeWrapper(requireContext(), R.style.MyAlertDialogStyle))
            .inflate(R.layout.fragment_dialog, null, false)
    dialog = AlertDialog
            .Builder(requireContext())
            .setView(dialogView)
            .create()

    dialog.setOnShowListener {
        childFragmentManager
                  .beginTransaction()
                  .replace(R.id.container, MyNestedFragmentInsideTheDialog())
                  .commit()
        // retrieve dialog buttons if any to manage onClickListener yourself
        // dialog.getButton(AlertDialog.BUTTON_POSITIVE)
    }
  }

}

这个想法是自己填充对话框内容并保留对它的引用,以便可以将其返回到onCreateView中,然后由子片段管理器使用 ;-)


-1

你可以创建变量,并将条件放在try和catch之间

  val comeFrom = try {
                args.comeFrom.toString()
            }catch (e:Exception){
                "0"
            }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接