允许 DialogFragment 接受外部触摸

18

我的应用程序中有一个显示 DialogFragmentFragment
在该片段中,我有一个关闭对话框的按钮。但是当我显示 DialogFragment 时,触摸对话框外部无效,并且我无法点击对话框片段外部的按钮。

如何允许对 DialogFragment 外部的触摸?

9个回答

26
为了实现这一点,需要打开一个允许外部触摸的Window标识,并清除背景暗淡标识以获得良好的外观。
由于必须在对话框创建后执行,因此我通过Handler实现它。
@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    // This is done in a post() since the dialog must be drawn before locating.
    getView().post(new Runnable() {

        @Override
        public void run() {

            Window dialogWindow = getDialog().getWindow();

            // Make the dialog possible to be outside touch
            dialogWindow.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
            dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);

            getView().invalidate();
        }
    });
}

此刻可以接触到外部触摸。

如果我们想让它更好看且不带框架,可以添加以下代码:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Hide title of the dialog
    setStyle(STYLE_NO_FRAME, 0);
}

12
你知道如何实现BottomSheetDialogFragment的相同效果吗? - elementstyle
@Yanix 当DialogFragment设置为全屏时,此代码无法正常工作。 window.setLayout(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) - Arun PS
这在 Android 6、7、7.1、8 上可行,但在 Android 9 上失败。在 Android 9 上,对话框片段以外的点击都不会被传递。 - Leszek

7

这是一种更通用的标记,允许任何操作而不仅限于触摸操作。

window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

1

我找到了一种处理触摸屏幕外部取消的替代方法。请查看下面的代码示例,这肯定有效。

@Override
public void onStart() {
    // mDialogView is member variable
    mDialogView = getView();
    mDialogView.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            float eventX = event.getRawX();
            float eventY = event.getRawY();

            int location[] = new int[2];
            mDialogView.getLocationOnScreen(location);

            if (eventX < location[0] || eventX > (location[0] + mDialogView.getWidth()) || eventY < location[1]
                    || eventY > location[1] + mDialogView.getHeight()) {
                dismiss();
                return true;
            }
            return false;
        }
    });
}

在我的情况下,我得到了非空值的getDialog(),但是getDialog().dismiss()不起作用。而且,标记为正确的解决方案也在我的情况下不起作用。因此,我采用了这种方法。

1

@yaniv已经提供了答案。但是如果您想要,这里有一个片段,您可以在不需要将Runnable发布到根View的情况下获得相同的结果:

@NonNull
@Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
    final Dialog dialog = super.onCreateDialog(savedInstanceState);
    dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
    return dialog;
}

1

我已经在编程方面测试了被接受的答案(Yaniv的答案)。

Galaxy Tab A 9.7 Android 7.1.1: ok
Galaxy Tab A 8.0 Android 7    : ok
Galaxy Tab S4 Android 9       : no touch outside DialogFragment
Galaxy S5 Android 6.0.1       : ok
Galaxy Tab S2 Android 7       : ok
Galaxy Tab S3 Android 9       : no touch outside DialogFragment
Galaxy Tab S4 Android 9       : no touch outside DialogFragment
Galaxy Note Edge Android 6.0.1: ok
Galaxy Note4 Android 6.0.1    : ok

看起来在Android 9上它似乎不起作用。当我说“不起作用”时,我的意思是当我尝试点击DialogFragment之外的按钮时,它的onClick()方法根本不会被调用。

编辑:根据我的发现(在评论中),我认为下面应该是可以接受的答案。在我测试过的所有地方,包括模拟器、Google Pixel 6、LG Nexus 5X、HTC Desire 12、约5个华为手机以及运行从Android 5到10的约20个三星手机上,下面的方法都能正常工作。

public Dialog onCreateDialog(Bundle savedInstanceState)
    {
    FragmentActivity act = getActivity();
    AlertDialog.Builder builder = new AlertDialog.Builder(act);

    // ... do your stuff here....

    Dialog dialog = builder.create();

    dialog.setCanceledOnTouchOutside(false);

    Window window = dialog.getWindow();

    if( window!=null )
      {
      window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                      WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
      window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
      }

    return dialog;
    }

它无法在Android 10及即将推出的Android 11上运行。总结一下:仅适用于Android 6、7、7.1、8.0、8.1,不适用于Android 9、10和11。 - Leszek
以上结果是在真实的三星手机上观察到的。有趣的是,当在Android 9、10或11的模拟器上运行时,接受的答案确实有效。 - Leszek
更好的是 - 我现在已经在运行Android 9的华为Mate Pro上测试过了,它能够工作。因此看起来这个问题只出现在运行Android 9、10或11的三星手机上。 - Leszek
好的,我终于找到了一种方法,使得被接受的答案在三星手机上也能够工作:将设置“FLAG_NOT_TOUCH_MODAL”标志的部分移动到onCreateDialog()的末尾,并且不要使用post() - 只需从那里调用它。看起来在三星手机上,这个标志需要尽早设置才能正常工作。 - Leszek

1

根据我所做的调查,我认为这应该是被接受的答案:

  @Override
  public Dialog onCreateDialog(Bundle savedInstanceState)
    {
    FragmentActivity act = getActivity();
    AlertDialog.Builder builder = new AlertDialog.Builder(act);

    // ... do your stuff here

    Dialog dialog = builder.create();

    dialog.setCanceledOnTouchOutside(false);

    Window window = dialog.getWindow();

    if( window!=null )
      {
      window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                      WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
      window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
      }

    return dialog;
    }

即将设置标志移到onCreateDialog()的末尾,并添加setCancelOnTouchOutside(false)。

接受的答案在运行Android 9和10的三星手机上不起作用(很可能也包括11)。看起来在这些系统上,三星已经改变了一些东西,使得只有在非常早期设置这些标志才能起作用。

以上内容适用于所有地方,包括Google模拟器、LG Nexus 5X、HTC Desire 12、Google Pixel 6、几款华为手机以及我测试过的所有(约20个)三星手机。


0

好的,让我们具体说明。 如果你想使用外部视图,那么你不应该使用对话框片段(Dialog Fragment),而应该像这样使用片段(Fragment)。

 FragmentTransaction transaction = fragmentManager.beginTransaction();
    // For a little polish, specify a transition animation
    transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
    // To make it fullscreen, use the 'content' root view as the container
    // for the fragment, which is always the root view for the activity
    transaction.add(android.R.id.content, newFragment)
               .addToBackStack(null).commit();

将布局设置为wrap_content,这样它看起来就像对话框,并且默认情况下它位于屏幕顶部。

更多详情请访问此处


0

您需要为您的需求设置适当的样式。您可以在官方文档中了解有关FragmentDialogs样式的更多信息。

我相信您可以使用以下样式:

  • STYLE_NO_FRAME

我已经在使用STYLE_NO_FRAME了,这是唯一一个对我来说没问题的(因为UI)。 - Yaniv

0

应该接受@Alexandre Bodi的答案。

在我的情况下,每次打开片段时Runnable()会使整个屏幕闪烁。 如果有人需要一个没有闪烁的干净的Kotlin方法,这里就是:

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    val dialog = super.onCreateDialog(savedInstanceState)
    dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
    dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
    return dialog
}

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