允许 DialogFragment 的外部触摸

Posted

技术标签:

【中文标题】允许 DialogFragment 的外部触摸【英文标题】:Allow outside touch for DialogFragment 【发布时间】:2013-03-01 05:22:42 【问题描述】:

我的应用中有一个Fragment,显示DialogFragment。 我在片段中有一个关闭对话框的按钮。但是当我显示 dialogFragment 时,对话框外部的触摸什么也不做,我无法单击对话框片段外部的按钮。

如何允许 DialogFragment 进行外部触摸?

【问题讨论】:

【参考方案1】:

为此,应打开允许外部触摸的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);

【讨论】:

你知道如何为 BottomSheetDialogFragment 实现同样的效果吗? @Yanix 这在 DialogFragment 设置为全屏时不起作用 window.setLayout(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) 这在 android 6、7、7.1、8 的情况下有效,但在 Android 9 的情况下失败。在 Android 9 上无法通过 DialogFragment 之外的任何点击。【参考方案2】:

这是一个更通用的标志,允许任何操作,而不仅仅是触摸操作

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

【讨论】:

【参考方案3】:

我找到了处理外部触摸解除的替代方法。请检查下面的代码示例,这肯定会起作用,

@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() 不起作用。此外,上面标记的解决方案在我的情况下也不起作用。所以,我采用了这种方法。

【讨论】:

【参考方案4】:

根据我上面的小调查,我真的认为这应该是公认的答案:

  @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;
    

即把flags的设置移到onCreateDialog()的最后,加上setCancelOnTouchOutside(false)。

接受的答案不适用于运行 Android 9 和 10(很可能是 11)的三星手机。看起来在这些系统上,三星改变了一些东西,使得设置这些标志只有在很早设置时才有效。

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

【讨论】:

【参考方案5】:

我已经在

上测试了接受的(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() 方法根本不会被调用。

编辑:根据我的发现(以 cmets 为单位),我认为以下应该是公认的答案。以下适用于我测试过的所有地方,包括模拟器、Google Pixel 6、LG Nexus 5X、HTC Desire 12、大约 5 部华为手机和大约 20 部运行 Android 5 到 10 的三星手机。

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。 以上结果是在真实的三星手机上观察到的。奇怪的是,当在 Android 9、10 或 11 的模拟器上运行时 - 接受的答案确实有效。 甚至更好 - 现在我已经在运行 Android 9 的华为 Mate Pro 上进行了测试,并且在那里它可以正常工作。所以看起来这不仅仅适用于运行 Android 9、10 或 11 的三星手机。 好吧,我终于找到了一种方法,让接受的答案也适用于三星手机:将设置“FLAG_NOT_TOUCH_MODAL”标志的部分移到 onCreateDialog() 的末尾,并且不要 post()它 - 只需从那里调用它。看起来在三星手机上,这个标志需要尽早设置才能工作。【参考方案6】:

您需要根据需要设置合适的样式。您可以在official docs 中阅读有关 FragmentDialogs 样式的更多信息。

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

STYLE_NO_FRAME

【讨论】:

我已经在使用 STYLE_NO_FRAME,它是唯一适合我的(自 UI 以来)。【参考方案7】:

好吧,让我们具体一点。 如果你想使用外部视图,那么你不应该使用对话框片段,而是像这样使用片段。

 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 布局,这显然看起来像 Dialog,默认情况下它位于屏幕顶部。

更多详情请访问here

【讨论】:

【参考方案8】:

@yaniv 提供了答案。但如果你愿意,这里有一个 sn-p,你可以得到相同的结果,但不需要将 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;

【讨论】:

以上是关于允许 DialogFragment 的外部触摸的主要内容,如果未能解决你的问题,请参考以下文章

DialogFragment findFragmentByTag 返回 null

关闭 dialogFragment 时键盘未关闭

重叠DialogFragment,在方向更改时以错误的顺序重新创建

ViewPage + DialogFragment + Bitmap项目实战

DialogFragment timepicker onCancel 和 onDismiss 问题

Android--从零单排系列--常用对话框和DialogFragment的优势