检测返回按钮,但不要关闭对话框片段

102

我有一个DialogFragment用于浮动对话框,其中包括一个特殊的键盘,当用户在EditText字段内按下时弹出(正常IME被阻止显示)。

我希望键盘在用户按下返回按钮时关闭(visibility = GONE),但对话框仍然可见,就像普通的IME服务一样。然而,据我所知,从我在SO和其他地方的相当广泛的阅读中,似乎没有办法做到这一点。

如果我将对话框设置为不可取消,则因为对话框不可取消,我不会收到onCancel()或onDismiss()的通知。

如果我将对话框设置为可取消,则会收到通知,但对话框将被关闭。

我无法在片段中的对话框中附加onKeyListener,因为系统会替换它,以便片段可以处理对话框的生命周期。

有没有办法做到这一点?或者对于片段系统的目的,是否已经完全封锁了键事件的检测权限?

15个回答

224

最佳且最干净的方法是在onCreateDialog()中覆盖onBackPressed()。

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    return new Dialog(getActivity(), getTheme()){
        @Override
        public void onBackPressed() {
            //do your stuff
        }
    };
}

10
但是在 DialogFragments 中无法使用此方法,因为 DialogFragment 类中没有 onBackPressed() 方法。 - Glen Pierce
15
DialogFragments包装了一个对话框 - onCreateDialog方法创建该对话框。它在DialogFragments中起作用。 - Tom
Dialog could also be an instance of AppCompatDialog - SoftWyer
由于某些原因,当您在BottomSheetDialogFragment中执行此操作时,对话框会变得混乱,主题和样式会被删除。 - Jenya Kirmiza
对于Kotlin:override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return object : Dialog(activity!!, theme) { override fun onBackPressed() { //做你的事情 } } } - Tushar Pandey
显示剩余4条评论

91

我曾经遇到和你相同的问题,后来通过将onKeyListener附加到DialogFragment来解决它。

在扩展了DialogFragment类的类的onResume()方法中,加入以下代码片段:

    getDialog().setOnKeyListener(new OnKeyListener()
    {
        @Override
        public boolean onKey(android.content.DialogInterface dialog, int keyCode,android.view.KeyEvent event) {

            if ((keyCode ==  android.view.KeyEvent.KEYCODE_BACK))
                {
                     //Hide your keyboard here!!!
                     return true; // pretend we've processed it
                }
            else 
                return false; // pass on to be processed as normal
        }
    });

在这里,你可能会遇到一个问题,那就是这段代码将会被执行两次:一次是当用户按下“返回”按钮时,另一次是当他离开按下它的状态时。在这种情况下,你需要按事件进行过滤:

@Override
public void onResume() {
    super.onResume();

    getDialog().setOnKeyListener(new OnKeyListener()
    {
        @Override
        public boolean onKey(android.content.DialogInterface dialog, int keyCode,
                android.view.KeyEvent event) {

            if ((keyCode ==  android.view.KeyEvent.KEYCODE_BACK))
            {
                //This is the filter
                if (event.getAction()!=KeyEvent.ACTION_DOWN)
                        return true;
                else
                {
                    //Hide your keyboard here!!!!!!
                    return true; // pretend we've processed it
                }
            } 
            else 
                return false; // pass on to be processed as normal
        }
    });
}

我没有使用过滤器,而是添加了getDialog().setOnKeyListener(null),以防止第二次调用。 - Lucas Arrefelt

31

作为对Juan Pedro Martinez答案的补充,我认为澄清一个具体问题(我个人也有这个问题)会很有帮助。

如果您希望创建一个新的DialogFragment,并使用户只能使用后退按钮取消它,以消除随机屏幕触摸会过早取消片段的影响,则需要使用以下代码。

在调用DialogFragment的任何代码中,您需要将可取消设置为false,以便没有任何东西可以解散该片段,不会出现任何偏离屏幕触摸等情况。

DialogFragment mDialog= new MyDialogFragment();
mDialog.setCancelable(false);
mDialog.show(getFragmentManager(), "dialog");

然后,在您的DialogFragment中,例如MyDaialogFragment.java文件中,您添加onResume重写代码,让对话框监听返回按钮。当按下该按钮时,它将执行dismiss()以关闭该片段。

@Override
 public void onResume() 
 {
     super.onResume();

     getDialog().setOnKeyListener(new OnKeyListener()
     {
         @Override
         public boolean onKey(android.content.DialogInterface dialog, 
                              int keyCode,android.view.KeyEvent event) 
         {
              if ((keyCode ==  android.view.KeyEvent.KEYCODE_BACK))
              {
                   // To dismiss the fragment when the back-button is pressed.
                   dismiss();
                   return true;
              }
              // Otherwise, do nothing else
              else return false;
         }
   });
}

现在你的对话框会被调用,并将“setCancelable”设置为false,这意味着没有任何东西(包括外部点击)可以取消它并关闭它,只允许从对话框内部使用返回按钮来关闭它。

加油!


1
非常好,谢谢。我想要在保持返回按钮功能的同时防止意外的外部点击,对于我来说,我只需要在DialogFragment的onCreateView方法中设置dialog实例的setCancelable(false)即可。 - Meanman

28
没有人提过这个吗?这是有关IT技术的内容。
public Dialog onCreateDialog(Bundle savedInstanceState) {
  Dialog dialog = super.onCreateDialog(savedInstanceState);

  // Add back button listener
  dialog.setOnKeyListener(new Dialog.OnKeyListener() {
    @Override
    public boolean onKey(DialogInterface dialogInterface, int keyCode, KeyEvent keyEvent) {
      // getAction to make sure this doesn't double fire
      if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.getAction() == KeyEvent.ACTION_UP) {
        // Your code here
        return true; // Capture onKey
      }
      return false; // Don't capture
    }
  });

  return dialog;
}

23

使用Fragment的onCancel覆写方法。当您按下返回键时,它会被调用。 以下是一个示例:

@Override
public void onCancel(DialogInterface dialog) {
    super.onCancel(dialog);

    // Add you codition
}

3
请注意,使用这种方法无法控制 DialogFragment 是否会被解除。您只能得到通知它将要被解除。 - Leo DroidCoder
确实,@Leo Droidcoder,有没有想法在对话框片段无法解除之前拦截返回按钮按下的方法? - matdev

7
自从AppCompat 1.5.0-alpha01起,AppCompatDialog现在扩展了ComponentDialog (来源)。这意味着在您的DialogFragment中,您现在可以轻松获取有效的OnBackPressedDispatcher(来自片段的对话框),以便您可以自行处理返回操作。 :
class MyFragment: androidx.fragment.app.DialogFragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        (dialog as androidx.activity.ComponentDialog)
            .onBackPressedDispatcher
            .addCallback(viewLifecycleOwner) {
                // handle back press
            }
    }
}

注意:当您添加回调函数时,按下“返回”按钮将阻止对话框自动取消/关闭。


到目前为止,这应该是正确的答案。 - Fred.Taiwan

5

创建对话框时,需要覆盖onBackPressed和onTouchEvent:

        final Dialog dialog = new Dialog(activity) {
            @Override
            public boolean onTouchEvent(final MotionEvent event) {
                //note: all touch events that occur here are outside the dialog, yet their type is just touching-down
                boolean shouldAvoidDismissing = ... ;
                if (shouldAvoidDismissing) 
                    return true;
                return super.onTouchEvent(event);
            }

            @Override
            public void onBackPressed() {
                boolean shouldAvoidDismissing = ... ;
                if (!shouldSwitchToInviteMode)
                    dismiss();
                else
                    ...
            }
        };

4
试试这个,然后回来给我的评论点赞 :D
/**
 * Callback when Back button is pressed.
 * By default it gonna call back press of host activity
 */
protected open fun onBackPressed() {
    requireActivity().onBackPressed()
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return object : BottomSheetDialog(context!!, theme) {
        override fun onBackPressed() {
            this@BaseBottomSheetFragment.onBackPressed()
        }

        override fun setOnKeyListener(onKeyListener: DialogInterface.OnKeyListener?) {
            //Do not call super function
            //This function do nothing but DON'T REMOVE this.
            //Try to set null for onKeyListener is not working.
        }
    }
}

enter image description here


4
延伸 Juan Pedro Martinez 上面的答案。我编写了一个扩展,可以在 onCreate() 中设置一个 DialogFragment,它将自动设置键监听器并根据生命周期删除它。
fun DialogFragment.setOnBackPressListener(onBackPress: () -> Boolean) {
    val listener = DialogInterface.OnKeyListener { _, keyCode, event ->
        if (keyCode == KeyEvent.KEYCODE_BACK && event?.action != KeyEvent.ACTION_DOWN) {
            onBackPress()
        } else {
            false
        }
    }
    val observer = object : LifecycleObserver {
        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        fun onResume() {
             dialog?.setOnKeyListener(listener)
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        fun onPause() {
             dialog?.setOnKeyListener(null)
        }
    }
    lifecycle.addObserver(observer)
}

在DialogFragment的onCreate方法中的使用。
setOnBackPressListener {
    // handle back press here

    true // return true to indicate back press was handled, false if not
}

4

防止 DialogFragment 被取消:

dialog.setCanceledOnTouchOutside(false)
dialog.setCancelable(false)
dialog.setOnKeyListener { dialog, keyCode, event ->
    keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP
}

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