检测后退按钮,但不要关闭对话框碎片

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

我希望当用户按下后退按钮(就像一个正常的 IME 服务一样)但是对话框仍然可见时,键盘被解除(可见性 = GONE)。然而,就我对 SO 和其他方面的相当广泛的阅读所见,似乎没有一种方法可以做到这一点。

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

如果我将对话框设置为可取消,那么我会得到通知,但是对话框将被取消。

我无法将 onKeyListener 附加到片段中的对话框,因为它已被系统替换,以便片段能够处理对话框的生命周期。

有什么办法吗?或者为了碎片系统的目的,关键事件检测的访问权限被完全隔离了?

69212 次浏览

I had the same problem than you and I've fixed it attaching the onKeyListener to the dialogfragment.

In the method onResume() of the class that extend of DialogFragment put these piece of code:

    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
}
});

Here one of the problems that you can find is this code is going to be executed twice: one when the user press tha back button and another one when he leave to press it. In that case, you have to filter by event:

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

As an addendum to Juan Pedro Martinez's answer I thought it would be helpful to clarify a specific question (one that I had) when looking at this thread.

If you wish to create a new DialogFragment and have it so the user can only cancel it using the back-button, which eliminates random screen touches from canceling the fragment prematurely, then this is the code that you would use.

In what ever code that you call the DialogFragment you need to set the cancelable setting to false so that NOTHING dismisses the fragment, no stray screen touches, etc.

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

Then, within your DialogFragment, in this case MyDaialogFragment.java, you add the onResume override code to have the dialog listen for the Back Button. When it's pressed it will execute the dismiss() to close the fragment.

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

Now your dialog will be called with the "setCancelable" to false, meaning nothing (no outside clicks) can cancel it and shut it down, and allowing (from within the dialog itself) only the back button to close it.

Ganbatte!

The best way and cleanest way is to override onBackPressed() in the dialog you created in onCreateDialog().

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

When creating the dialog, override both onBackPressed and 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
...
}
};

How has no one suggested this?

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

Use onDismiss() callback of DialogFragment with a closeActivity flag

private var closeActivity: Boolean = true


override fun onDismiss(dialog: DialogInterface?) {
super.onDismiss(dialog)


if (closeActivity) {
activity!!.finish()
}
}

Use Fragment onCancel override method. It's called when you press back. here is a sample:

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


// Add you codition
}

Prevent canceling DialogFragment:

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

AndroidX OnBackPressedDispatcher can also be option for someone

Extending off Juan Pedro Martinez's answer above. I wrote an extension on a DialogFragment that one could set from the onCreate() which will automatically set the key listener and remove it based off the lifecycle.

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)
}

Usage in DialogFragment#onCreate

setOnBackPressListener {
// handle back press here


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

Try this and back to upvote my comment :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

Since AppCompat 1.5.0-alpha01 AppCompatDialog is now extending ComponentDialog (source). That means in your DialogFragment you can now easily get a valid OnBackPressedDispatcher (from the fragment's dialog) that allows you to handle Back presses yourself.

kotlin:

class MyFragment: androidx.fragment.app.DialogFragment() {


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

Note: when you add the callback it prevents the dialog from being dismissed/cancelled automatically by pressing Back.

It's a bit tricky here because we don't get any listner or callbacks on dialog fragment. On way to do it is to override the onDismiss() which will work as system callback which tells that dialog is dismissed and based on that we can setup our own custom callback as follow

override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
debugLog("Dialog is dismissed")
dismiss()
listener?.onRemoteBackPressed()
}