从片段中显示对话框?

123

我有一些片段需要显示一个常规对话框。在这些对话框上,用户可以选择是/否答案,然后片段应该相应地行动。

现在,Fragment类没有onCreateDialog()方法可覆盖,所以我想我必须在包含的Activity外实现对话框。这没问题,但是Activity需要以某种方式向片段报告所选答案。当然,我可以在此处使用回调模式,因此片段在监听器类中向Activity注册自己,而 Activity 将通过该方式报告回答,或者类似于此。

但是,对于像在片段中显示“简单”的是/否对话框这样简单的任务来说,这似乎是一场大混乱。而且,这样一来我的Fragment将不太自包含。

有没有更干净的方法来做到这一点?

编辑:

这个问题的答案并没有详细解释应该如何使用 DialogFragments 从 Fragments 显示对话框。所以据我所知,应该采取以下步骤:

  1. 显示一个 Fragment。
  2. 在需要时,实例化一个 DialogFragment。
  3. 使用.setTargetFragment()将原始 Fragment 设置为此 DialogFragment 的目标。
  4. 从原始 Fragment 中使用 .show() 显示 DialogFragment。
  5. 当用户在此 DialogFragment 上选择某个选项时,通知原始 Fragment 进行此选择(例如,用户单击了“是”),您可以使用 .getTarget() 获取原始 Fragment 的引用。
  6. 关闭 DialogFragment。

1
你的技术很好,但当屏幕旋转时会出现强制关闭。有什么想法吗? - Weston
@Weston 请查看Zsombor的第一个答案:https://dev59.com/kmsy5IYBdhLWcg3w6iNZ - mightimaus
7个回答

40

我必须谨慎地怀疑之前被接受的答案,即使用DialogFragment是最好的选项。 DialogFragment的预期(主要)目的似乎是显示本身就是对话框的片段,而不是显示具有要显示的对话框的片段。

我认为使用片段的活动来调解对话框和片段之间的关系是更可取的选择。


10
由于onCreateDialog方法即将被弃用,因此我不同意这种管理对话框的方法,我认为使用DialogFragment是正确的选择。 - Dave
4
被接受的答案意味着使用DialogFragment而不是Dialog,而不是替换ListFragment,据我所知。 - nmr
1
@anoniim 这取决于对话框片段的使用方式。如果它被用作常规片段,那么没问题。但如果它被用作对话框,那么是的,你不应该显示另一个对话框。 - Clive Jefferies
1
只要启动的片段不是对话框,那么是的。我不确定ChildFragmentManager,我使用SupportFragmentManager,这很好用。所有的信息都在文档中。 - Clive Jefferies
1
这个答案在各个方面都完美地适用于我。 - Matt Payne
显示剩余3条评论

37

10
不幸的是,这种方法比之前 Android 版本的经典管理对话框方式更冗长,但现在它是首选方法。您可以通过使用 FragmentManagerputFragmentgetFragment 方法完全避免引用 Activity,使得 DialogFragment 可以直接向调用片段报告(即使方向发生改变)。 - Dave
如果您有一个需要显示对话框的ListFragment,无法同时扩展它们。 - marchinram
16
ListFragment的子类应该通过实例化新的DialogFragments来使用它们,而不是通过子类化DialogFragment来使用它们。DialogFragment是一种实现为Fragment的对话框,而不是可以显示对话框的Fragment。 - nmr
4
好的,以下是需要翻译的内容:请添加一些片段,以便我们可以轻松理解。 - Arpit Patel

26

这是一个完整的yes/no DialogFragment示例:

该类:

public class SomeDialog extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
            .setTitle("Title")
            .setMessage("Sure you wanna do this!")
            .setNegativeButton(android.R.string.no, new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // do nothing (will close dialog)
                }
            })
            .setPositiveButton(android.R.string.yes,  new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // do something
                }
            })
            .create();
    }
}

开始对话:

        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        // Create and show the dialog.
        SomeDialog newFragment = new SomeDialog ();
        newFragment.show(ft, "dialog");

您还可以让类实现onClickListener并使用它来代替嵌入式监听器。

回调到Activity

如果您想要实现回调,请按照以下步骤进行操作,在您的Activity中:

YourActivity extends Activity implements OnFragmentClickListener

@Override
public void onFragmentClick(int action, Object object) {
    switch(action) {
        case SOME_ACTION:
        //Do your action here
        break;
    }
}

回调函数类:

public interface OnFragmentClickListener {
    public void onFragmentClick(int action, Object object);
}

如果要从片段执行回调,则需要确保已像这样附加侦听器:

然后,要从一个片段中执行回调,您需要确保侦听器已经附加了,如下所示:

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    try {
        mListener = (OnFragmentClickListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString() + " must implement listeners!");
    }
}

回调函数的执行方式如下:

mListener.onFragmentClick(SOME_ACTION, null); // null or some important object as second parameter.

5
这并没有解释如何从一个Fragment开始启动它。 - akohout
@raveN,你只需回调到你的活动,然后启动片段即可。 - Warpzit
@Warpzit 感谢您提供全面的答案。 我很害怕触及FragmentManager,因为我已经在做一些不可思议的事情了... 我该如何将onClick事件传递回需要用户输入的(非Dialog)Fragment? 顺便说一句,这个事务不应该被添加到后退栈中吗? - kaay
是的,这很棒。诀窍在于回调到Activity,并小心地更新每次系统重新创建Activity / Fragment对时更新哪个Activity。尽管它们提供了许多好处,但Fragment确实也带来了痛苦之海。 - Richard Le Mesurier
1
@RichardLeMesurier 的确,代码片段有时候会让人高兴,有时候会让人沮丧。 - Warpzit
显示剩余4条评论

14

对我来说,它是以下内容 -

MyFragment:

public class MyFragment extends Fragment implements MyDialog.Callback
{
    ShowDialog activity_showDialog;

    @Override
    public void onAttach(Activity activity)
    {
        super.onAttach(activity);
        try
        {
            activity_showDialog = (ShowDialog)activity;
        }
        catch(ClassCastException e)
        {
            Log.e(this.getClass().getSimpleName(), "ShowDialog interface needs to be     implemented by Activity.", e);
            throw e;
        }
    }

    @Override
    public void onClick(View view) 
    {
        ...
        MyDialog dialog = new MyDialog();
        dialog.setTargetFragment(this, 1); //request code
        activity_showDialog.showDialog(dialog);
        ...
    }

    @Override
    public void accept()
    {
        //accept
    }

    @Override
    public void decline()
    {
        //decline
    }

    @Override
    public void cancel()
    {
        //cancel
    }

}

我的对话框:

public class MyDialog extends DialogFragment implements View.OnClickListener
{
    private EditText mEditText;
    private Button acceptButton;
    private Button rejectButton;
    private Button cancelButton;

    public static interface Callback
    {
        public void accept();
        public void decline();
        public void cancel();
    }

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View view = inflater.inflate(R.layout.dialogfragment, container);
        acceptButton = (Button) view.findViewById(R.id.dialogfragment_acceptbtn);
        rejectButton = (Button) view.findViewById(R.id.dialogfragment_rejectbtn);
        cancelButton = (Button) view.findViewById(R.id.dialogfragment_cancelbtn);
        acceptButton.setOnClickListener(this);
        rejectButton.setOnClickListener(this);
        cancelButton.setOnClickListener(this);
        getDialog().setTitle(R.string.dialog_title);
        return view;
    }

    @Override
    public void onClick(View v)
    {
        Callback callback = null;
        try
        {
            callback = (Callback) getTargetFragment();
        }
        catch (ClassCastException e)
        {
            Log.e(this.getClass().getSimpleName(), "Callback of this class must be implemented by target fragment!", e);
            throw e;
        }

        if (callback != null)
        {
            if (v == acceptButton)
            {   
                callback.accept();
                this.dismiss();
            }
            else if (v == rejectButton)
            {
                callback.decline();
                this.dismiss();
            }
            else if (v == cancelButton)
            {
                callback.cancel();
                this.dismiss();
            }
        }
    }
}

活动:

public class MyActivity extends ActionBarActivity implements ShowDialog
{
    ..

    @Override
    public void showDialog(DialogFragment dialogFragment)
    {
        FragmentManager fragmentManager = getSupportFragmentManager();
        dialogFragment.show(fragmentManager, "dialog");
    }
}

DialogFragment布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/dialogfragment_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="10dp"
        android:text="@string/example"/>

    <Button
        android:id="@+id/dialogfragment_acceptbtn"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:layout_centerHorizontal="true"
        android:layout_below="@+id/dialogfragment_textview"
        android:text="@string/accept"
        />

    <Button
        android:id="@+id/dialogfragment_rejectbtn"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_alignLeft="@+id/dialogfragment_acceptbtn"
        android:layout_below="@+id/dialogfragment_acceptbtn"
        android:text="@string/decline" />

     <Button
        android:id="@+id/dialogfragment_cancelbtn"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="20dp"
        android:layout_alignLeft="@+id/dialogfragment_rejectbtn"
        android:layout_below="@+id/dialogfragment_rejectbtn"
        android:text="@string/cancel" />

     <Button
        android:id="@+id/dialogfragment_heightfixhiddenbtn"
        android:layout_width="200dp"
        android:layout_height="20dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="20dp"
        android:layout_alignLeft="@+id/dialogfragment_cancelbtn"
        android:layout_below="@+id/dialogfragment_cancelbtn"
        android:background="@android:color/transparent"
        android:enabled="false"
        android:text=" " />
</RelativeLayout>

正如名称dialogfragment_heightfixhiddenbtn所示,我无法找到一种方法来修复底部按钮的高度被削减了一半的问题,即使设置为wrap_content,因此我添加了一个隐藏按钮来代替被削减的那一半。对于这种折中方案给您带来的不便,非常抱歉。


1
当系统在旋转后重新启动Activity/Fragment集时,使用setTargetFragment()设置的引用集会被正确地重新创建。因此,该引用将自动指向新的目标。 - Richard Le Mesurier
我现在真希望早些时候使用ButterKnife,否则我会打自己的鼻子。使用ButterKnife可以让你的视图处理更加漂亮。 - EpicPandaForce
你可以使用Otto在Fragment和Activity之间发送事件,这样你就不需要使用接口附加魔法了。这里有一个Otto的例子:https://dev59.com/dYfca4cB1Zd3GeqPns75#28480952 - EpicPandaForce
@EpicPandaForce 请问您能否添加“ShowDialog”接口/类?这是您的示例中唯一缺少的东西。 - ntrch
@ntrch 这是一个公共接口 ShowDialog,其中包含方法 void showDialog(DialogFragment dialogFragment); - EpicPandaForce
@EpicPandaForce 当然,谢谢。我以为是因为界面的问题,但实际上是由于应用程序库而不是v4引起的。任何仍然遇到与v4导入相关的错误的人,请确保您导入的FragmentManager和DialogFragment版本相同。 - ntrch

4
我自己也是一个初学者,老实说我找不到一个令我满意且易于理解或实施的答案。因此,这里有一个外部链接,它真正帮助我实现了我想要的目标。它非常直接和易于跟随。

http://www.helloandroid.com/tutorials/how-display-custom-dialog-your-android-application

我希望通过代码实现以下内容:

我有一个MainActivity,其中包含一个Fragment。我想让一个对话框出现在布局的顶部,以请求用户输入并相应地处理输入。 请查看屏幕截图

这是我的片段的onCreateView方法:

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

    View rootView = inflater.inflate(R.layout.fragment_home_activity, container, false);

    Button addTransactionBtn = rootView.findViewById(R.id.addTransactionBtn);

    addTransactionBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Dialog dialog = new Dialog(getActivity());
            dialog.setContentView(R.layout.dialog_trans);
            dialog.setTitle("Add an Expense");
            dialog.setCancelable(true);

            dialog.show();

        }
    });

我希望这将对您有所帮助。

如果有任何疑惑,请告诉我。 :)


3
 public void showAlert(){


     AlertDialog.Builder alertDialog = new AlertDialog.Builder(getActivity());
     LayoutInflater inflater = getActivity().getLayoutInflater();
     View alertDialogView = inflater.inflate(R.layout.test_dialog, null);
     alertDialog.setView(alertDialogView);

     TextView textDialog = (TextView) alertDialogView.findViewById(R.id.text_testDialogMsg);
     textDialog.setText(questionMissing);

     alertDialog.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
         public void onClick(DialogInterface dialog, int which) {
             dialog.cancel();
         }
     });
     alertDialog.show();

}

其中 .test_dialog 是自定义的 XML


0
    public static void OpenDialog (Activity activity, DialogFragment fragment){

    final FragmentManager fm = ((FragmentActivity)activity).getSupportFragmentManager();

    fragment.show(fm, "tag");
}

3
请提供翻译后的文本。不要解释原文。 - RealCheeseLord

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