自定义DialogFragment布局:OnCreateView与OnCreateDialog的区别

91

我正在尝试使用自己的布局创建DialogFragment。

我看到了几种不同的方法。有时在OnCreateDialog中设置布局,像这样: (我正在使用Mono,但我已经习惯了Java)

public override Android.App.Dialog OnCreateDialog (Bundle savedInstanceState)
{
    base.OnCreateDialog(savedInstanceState);
    AlertDialog.Builder b = new AlertDialog.Builder(Activity);
        //blah blah blah
    LayoutInflater i = Activity.LayoutInflater;
    b.SetView(i.Inflate(Resource.Layout.frag_SelectCase, null));
    return b.Create();
}

这个第一种方法对我来说很有效...直到我想使用 findViewByID

所以,在经过一番搜索后,我尝试了第二种方法,它涉及覆盖 OnCreateView

因此,我注释了两行设置布局的 OnCreateDialog 代码,并添加了以下内容:

public override Android.Views.View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    View v = inflater.Inflate(Resource.Layout.frag_SelectCase, container, false);
        //should be able to use FindViewByID here...
    return v;
}

这给了我一个可爱的错误:

11-05 22:00:05.381: E/AndroidRuntime(342): FATAL EXCEPTION: main
11-05 22:00:05.381: E/AndroidRuntime(342): android.util.AndroidRuntimeException: requestFeature() must be called before adding content

我被难住了。


已经很晚了,但仍然发布类似的内容:https://dev59.com/H3vaa4cB1Zd3GeqPEo92#30890121 - Raghav Sharma
6个回答

57

我使用下面的代码时遇到了相同的异常:

public class SelectWeekDayFragment extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
        .setMessage("Are you sure?").setPositiveButton("Ok", null)
        .setNegativeButton("No way", null).create();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.week_day_dialog, container, false);

        return view;    
    }
}

在DialogFragment中,您必须选择仅覆盖onCreateView或onCreateDialog中的一个。同时覆盖两个将导致异常:“必须在添加内容之前调用requestFeature()”。

重要提示

有关完整答案,请查看@TravisChristian的评论。正如他所说,您确实可以同时覆盖两者,但当您尝试在已创建对话框视图后再次填充视图时,就会出现问题。


34
那不完全正确。你可以覆盖两者(实际上 DialogFragment 是这样说的),问题出在你尝试在已经创建对话框视图之后再加载视图时。你仍然可以在 onCreateView 中执行其他操作,比如使用 savedInstanceState,而不会导致异常。 - Travis Christian
3
我也需要支持两者。这就是嵌入片段或对话框的想法。这对我来说似乎是个错误。我所能做的最好的办法,就是为对话框设置标题,但是在调用super并将标题设置为返回的对话框对象时,没有成功添加取消按钮: final Dialog dialog = super.onCreateDialog(savedInstanceState); dialog.setTitle(m_callback.getTitle()); // 没有成功添加取消按钮 return dialog; - farid_z

38

这种方法对我有用...直到我想使用FindViewById。

我猜你没有将findViewById()限定为inflate()返回的视图,试试这样:

View view = i.inflate(Resource.Layout.frag_SelectCase, null);
// Now use view.findViewById() to do what you want
b.setView(view);

return b.create();

1
这确实有效。谢谢!我仍然很好奇为什么OnCreateView会崩溃。 - gghuffer
2
@gghuffer 虽然已经晚了4个月,但我认为这个异常并不是由上面的代码直接引起的。更常见的情况是人们在添加内容后调用 requestFeature(...)(或类似于 requestWindowFeature(Window.FEATURE_NO_TITLE); 的东西),正如异常消息所述。 - Gerrit-K

36

以下代码来自Google指南,因此答案是您不能在onCreateDialog()中像您的方式那样操作,您必须使用super.onCreateDialog()获取对话框。

public class CustomDialogFragment extends DialogFragment {
    /** The system calls this to get the DialogFragment's layout, regardless
        of whether it's being displayed as a dialog or an embedded fragment. */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // Inflate the layout to use as dialog or embedded fragment
        return inflater.inflate(R.layout.purchase_items, container, false);
    }

    /** The system calls this only when creating the layout in a dialog. */
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // The only reason you might override this method when using onCreateView() is
        // to modify any dialog characteristics. For example, the dialog includes a
        // title by default, but your custom layout might not need it. So here you can
        // remove the dialog title, but you must call the superclass to get the Dialog.
        Dialog dialog = super.onCreateDialog(savedInstanceState);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        return dialog;
    }
}

2
你有链接吗? - Daniel Gomez Rico
@Zebphyr 如果我想将这个CustomDialogFragment作为我的Activity的对话框使用,但是在上面的代码中onCreateDialog()方法中没有提到R.layout.my_layout,那么onCreateView()方法会有帮助吗? - CopsOnRoad
@Jack Jan,是的,你可以在onCreateView()调用中指定布局文件。 - Zephyr

19

这里是在对话框片段中使用 findViewById 的示例

public class NotesDialog extends DialogFragment {

        private ListView mNotes;
       private RelativeLayout addNote;

        public NotesDialog() {
            // Empty constructor required for DialogFragment
        }



        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {

            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

            View view = getActivity().getLayoutInflater().inflate(R.layout.note_dialog, null);
            mNotes = (ListView) view.findViewById(R.id.listViewNotes);
            addNote = (RelativeLayout) view.findViewById(R.id.notesAdd);

            addNote.setOnClickListener(new View.OnClickListener(){
                 @Override
                 public void onClick(View v){


                     getDialog().dismiss();

                     showNoteDialog();
                 }
             });

            builder.setView(view);

            builder.setTitle(bandString);


            builder.setNegativeButton("Cancel",
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int whichButton) {
                          getDialog().dismiss();
                        }
                    }
                );


           return  builder.create();


    }

1
这个例子对我来说完美无缺。你必须把所有的代码放到onCreateDialog中,而不是从onCreateView中。这段代码允许用户做到这一点,并获得按钮。完美! - Brandon

11
正如 @Xavier Egea 所说,如果你同时实现了 onCreateView() 和 onCreateDialog(),你有可能会遇到“必须在添加内容之前调用 requestFeature()”崩溃的风险。这是因为当你将该片段作为对话框显示时,会先调用 onCreateDialog(),然后再调用 onCreateView() (为什么,我不知道)。正如 Travis Christian 所提到的,在 onCreateDialog() 中创建对话框后,在 onCreateView() 中使用 inflate() 会导致崩溃。

一种实现这两个函数但避免此类崩溃的方法:使用 getShowsDialog() 来限制你的 onCreateView() 的执行(这样就不会调用你的 inflate())。这样,当你将 DialogFragment 作为对话框显示时,只有你的 onCreateDialog() 代码被执行,但当你的 DialogFragment 作为布局中的片段使用时,则可以调用你的 onCreateView() 代码。
// Note: if already have onCreateDialog() and you only ever use this fragment as a 
// dialog, onCreateView() isn't necessary
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if (getShowsDialog() == true) {  // **The key check**
        return super.onCreateView(inflater, container, savedInstanceState);
    } else {
        View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_alarm_dialog, null);    
        return configureDialogView(view);
    }
}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{ 
    // Return custom dialog...
    Dialog dialog = super.onCreateDialog(savedInstanceState); // "new Dialog()" will cause crash

    View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_alarm_dialog, null);    
    configureDialogView(view);
    dialog.setContentView(view);

    return dialog;
}

// Code that can be reused in both onCreateDialog() and onCreateView()
private View configureDialogView(View v) {      
    TextView myText = (TextView)v.findViewById(R.id.myTextView);
    myText.setText("Some Text");

    // etc....

    return v;
}

我不明白为什么要这样做,因为onCreateView已经在配置视图了。为什么你想在两个地方都配置视图呢?你可以将onCreateView作为通用的膨胀代码,并且它将在对话框中被膨胀。 - user1530779
@user1530779 按钮怎么办?在OnCreateDialog中,我可以使用builder设置按钮,但是在OnCreateView中,当对话框中的视图被填充时,我应该怎么做才能获取按钮? - Varvara Kalinina
嗯,似乎使用Builder会导致异常。那么如果视图在对话框中被填充时设置按钮,而在片段中不设置按钮,应该怎么做呢? - Varvara Kalinina
好的,事实证明,如果您调用dialog.setView而不是dialog.setContentView,即使您使用Builder创建对话框并设置按钮,这种方法也可以正常工作。 - Varvara Kalinina

7
如果您想方便地访问对话框属性,例如标题和取消按钮,但同时也想使用自己的布局,则可以在覆盖onCreateDialog时使用LayoutInflator与Builder一起使用。
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    LayoutInflater inflater = getActivity().getLayoutInflater();
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    builder.setMessage("Message!")
        .setTitle(this.dialogTitle)
        .setView(inflater.inflate(R.layout.numpad_dialog, null))
        .setPositiveButton(R.string.enter, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                // Clicked 'Okay'
            }
        })
        .setNegativeButton(R.string.dismiss, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                // Clicked 'Cancel'
            }
        });
    return builder.create();
}

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