底部弹出对话框碎片 - 监听用户关闭事件

65

我如何监听BottomSheetDialogFragment的最终关闭?我只想在最终关闭时保存用户更改...

我尝试了以下方法:

方法1

这只有在通过向下滑动对话框关闭时才会触发(不是在返回按键或点击外部触摸时)

@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
    Dialog d = super.onCreateDialog(savedInstanceState);
    d.setOnShowListener(new DialogInterface.OnShowListener() {
        @Override
        public void onShow(DialogInterface dialog) {

            BottomSheetDialog d = (BottomSheetDialog) dialog;   
            FrameLayout bottomSheet = (FrameLayout) dialog.findViewById(android.support.design.R.id.design_bottom_sheet);

            BottomSheetBehavior behaviour = BottomSheetBehavior.from(bottomSheet);
            behaviour.setState(BottomSheetBehavior.STATE_EXPANDED);
            behaviour.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
                @Override
                public void onStateChanged(@NonNull View bottomSheet, int newState) {
                    if (newState == BottomSheetBehavior.STATE_HIDDEN)
                    {
                        // Bottom Sheet was dismissed by user! But this is only fired, if dialog is swiped down! Not if touch outside dismissed the dialog or the back button
                        Toast.makeText(MainApp.get(), "HIDDEN", Toast.LENGTH_SHORT).show();
                        dismiss();
                    }
                }

                @Override
                public void onSlide(@NonNull View bottomSheet, float slideOffset) {

                }
            });
        }
    });
    return d;
}

方法二

这个方法无法区分最终的解雇和来自屏幕旋转或活动重新创建的解雇...

 @Override
public void onDismiss(DialogInterface dialog)
{
    super.onDismiss(dialog);
    // this works fine but fires one time too often for my use case, it fires on screen rotation as well, although this is a temporarily dismiss only
    Toast.makeText(MainApp.get(), "DISMISSED", Toast.LENGTH_SHORT).show();
}

问题

我该如何监听表示用户已完成对话框的事件?

10个回答

105

虽然SO上的所有类似问题都建议使用onDismiss,但我认为以下是正确的解决方案:

@Override
public void onCancel(DialogInterface dialog)
{
    super.onCancel(dialog);
    Toast.makeText(MainApp.get(), "CANCEL", Toast.LENGTH_SHORT).show();
}

如果发生以下情况,则触发此操作:

* the user presses back
* the user presses outside of the dialog

这将不会触发:

* on screen rotation and activity recreation

解决方案

onCancelBottomSheetBehavior.BottomSheetCallback.onStateChanged 结合起来使用:

public class Dailog extends BottomSheetDialogFragment
{
    @Override
    public void onCancel(DialogInterface dialog)
    {
        super.onCancel(dialog);
        handleUserExit();
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState)
    {
        Dialog d = super.onCreateDialog(savedInstanceState);
        d.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;
                FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);
                BottomSheetBehavior behaviour = BottomSheetBehavior.from(bottomSheet);
                behaviour.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
                    @Override
                    public void onStateChanged(@NonNull View bottomSheet, int newState) {
                        if (newState == BottomSheetBehavior.STATE_HIDDEN)
                        {
                            handleUserExit();
                            dismiss();
                        }
                    }

                    @Override
                    public void onSlide(@NonNull View bottomSheet, float slideOffset) {

                    }
                });
            }
        });
        return d;
    }

    private void handleUserExit()
    {
        Toast.makeText(MainApp.get(), "TODO - SAVE data or similar", Toast.LENGTH_SHORT).show();
    }
}

我想知道为什么需要在onStateChanged()中调用dismiss()。如果没有调用dismiss(),下滑底部表格将无法完全工作。 - illusionJJ
1
感谢onCancel()的使用。警告!如果您禁用了BottomSheet的取消功能(参见https://dev59.com/8VgQ5IYBdhLWcg3w1nVA),那么这个解决方案和类似的解决方案将无效。如果您设置了`bottomSheetDialogFragment.setCancelable(false);`,监听器将不会被调用! - CoolMind

13

如果你从BottomSheetDialogFragment()继承,只需要在你的类中覆盖它即可。

 override fun onDismiss(dialog: DialogInterface) {
        super.onDismiss(dialog)
        //Code here
    }

onBackPress被触发或者你通过点击对话框外部来关闭对话框时,这将被触发。

请确保不要将你的对话框设置为可取消,因为这将不会触发。


9
bottomSheetDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
   @Override
   public void onDismiss(DialogInterface dialog) {
       toast("dismissed");
   }
});

6
不建议仅提供代码的答案。请解释您的答案为什么以及如何解决问题。 - Igor F.
重写 onDismiss 或 onCancel 更好。 - Sairaj Sawant
8
BottomSheetDialogFragment中似乎没有setOnDismissListener方法。 - YaMiN
可以使用 bottomSheet.dialog?.setOnDismissListener - Avinash Reddy

5

虽然@prom85的方法可行,但是还有另一种方法。如果你想在某些情况下关闭BottomSheetDialogFragment并在其他情况下保留它,那么这种方法就不起作用了,因为它会在所有情况下关闭对话框。

例如,如果您在BottomSheetDialogFragmentEditText中输入文本,并偶尔点击外部,它将在没有任何警告的情况下关闭对话框。我尝试过https://medium.com/@anitas3791/android-bottomsheetdialog-3871a6e9d538,它可以工作,但是在另一种情况下则不行。当您拖动对话框时,它将被关闭。如果您单击外部,则不会显示任何警告消息且不会关闭对话框。

所以,我使用了@shijo在https://dev59.com/8VgQ5IYBdhLWcg3w1nVA#50734566上给出的好建议。

将以下代码添加到onActivityCreated方法中(或者在onCreateView之后的任何其他生命周期方法中)。

@Override 
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    View touchOutsideView = getDialog().getWindow()
        .getDecorView()
        .findViewById(android.support.design.R.id.touch_outside);
    touchOutsideView.setOnClickListener(yourClickListener);
}

在我的情况下,在 yourClickListener 中,我会检查文本并显示警报或关闭对话框:

private fun checkAndDismiss() {
    if (newText == oldText) {
        dismissAllowingStateLoss()
    } else {
        showDismissAlert()
    }
}

当您创建BottomSheetDialogFragment时,请勿像https://dev59.com/8VgQ5IYBdhLWcg3w1nVA#42679131中所示调用setCancelable(false),否则这些方法可能不起作用。而且也许不要在样式中设置<item name="android:windowCloseOnTouchOutside">false</item>或者在onCreateDialog中使用setCanceledOnTouchOutside(false)
我也尝试了几种覆盖取消行为的方法,但都没有成功。
<style name="BottomSheetDialogTheme" parent="Theme.Design.Light.BottomSheetDialog">
    <item name="android:windowCloseOnTouchOutside">false</item>
</style>

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setStyle(STYLE_NORMAL, R.style.BottomSheetDialogTheme)
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    val dialog = super.onCreateDialog(savedInstanceState)

    dialog.setOnShowListener {
        val bottomSheet = dialog.findViewById<View>(
            android.support.design.R.id.design_bottom_sheet) as? FrameLayout
        val behavior = BottomSheetBehavior.from(bottomSheet)
        behavior.state = BottomSheetBehavior.STATE_EXPANDED
        behavior.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
            override fun onSlide(bottomSheet: View, slideOffset: Float) {
            }

            override fun onStateChanged(bottomSheet: View, newState: Int) {
                //showing the different states.
                when (newState) {
                    BottomSheetBehavior.STATE_HIDDEN -> dismiss() //if you want the modal to be dismissed when user drags the bottomsheet down
                    BottomSheetBehavior.STATE_EXPANDED -> {
                    }
                    BottomSheetBehavior.STATE_COLLAPSED -> {
                    }
                    BottomSheetBehavior.STATE_DRAGGING -> {
                    }
                    BottomSheetBehavior.STATE_SETTLING -> {
                    }
                }
            }
        })
        dialog.setOnCancelListener {
            // Doesn't matter what you write here, the dialog will be closed.
        }
        dialog.setOnDismissListener {
            // Doesn't matter what you write here, the dialog will be closed.
        }
    }

    return dialog
}

override fun onCancel(dialog: DialogInterface?) {
    // Doesn't matter what you write here, the dialog will be closed.
    super.onCancel(dialog)
}

override fun onDismiss(dialog: DialogInterface?) {
    // Doesn't matter what you write here, the dialog will be closed.
    super.onDismiss(dialog)
}

5
我使用了一个简单的技巧来实现这个。
val bottomSheetDialog = FeedbackFormsFragment.createInstance()
bottomSheetDialog.show((activity as FragmentActivity).supportFragmentManager, BOTTOM_SHEET)


// add some delay to allow the bottom sheet to be visible first so that the dialog is not null

                Handler().postDelayed({
                    bottomSheetDialog.dialog?.setOnDismissListener {

                       // add code here
                    }
                }, 1000)

1
LoctSetupDialog loctSetupDialog = new LoctSetupDialog();
        loctSetupDialog.show(requireActivity().getSupportFragmentManager(),"loctSetup");

        loctSetupDialog.getLifecycle().addObserver((LifecycleEventObserver) (source, event) -> {
            if(event == Lifecycle.Event.ON_DESTROY)
            {
                refreshLoctName();
            }
        });

使用生命周期。 - EPIS_
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

1

这段代码对我有效:

bottomSheetDialogFragment.getDialog().setOnDismissListener(dialog -> {
                // code goes here
            });

请注意,在显示bottomSheetDialogFragment(在我的情况下是showNow)后再调用它,否则getDialog()将返回null。

2
不工作的人 - KamDroid

0
在一个 AppCompatActivity 中,你可以使用以下技术:
    val mgr = supportFragmentManager
    val callback = object: FragmentManager.OnBackStackChangedListener {
        var count = 0
        override fun onBackStackChanged() {
            // We come here twice, once when the sheet is opened, 
            // once when it's closed.
            if (++count >= 2) {
                mgr.removeOnBackStackChangedListener(this)
                doWhatNeedsToBeDoneWhenTheSheetIsClosed()
            }
        }
    }
    mgr.addOnBackStackChangedListener(callback)

在调用show函数之前,请确保执行上述操作。


0
您可以添加一个LifecycleObserver,当LifecycleOwner状态发生更改时,将通知该观察者:
val yourBottomSheet = BottomSheet()
            
yourBottomSheet.lifecycle.addObserver(LifecycleEventObserver { source, event ->
      // events Lifecycle.Event.ON_CREATE, Lifecycle.Event.ON_DESTROY...
})

0
BottomSheetDialogFragment继承自DialogFragment,该类已经实现了两个接口:DialogInterface.OnCancelListener和DialogInterface.OnDismissListener。
你可以监听对话框的关闭事件:
override fun onCancel(dialog: DialogInterface) {
    super.onCancel(dialog)
    // you code here
}

并且

override fun onDismiss(dialog: DialogInterface) {
    super.onDismiss(dialog)
    // you code here
}

如果用户在对话框外点击关闭对话框,或者用户向下滑动对话框,或者用户点击返回按钮,还有如果您通过编程调用关闭对话框,
dialog.?cancel()

如果您以编程方式关闭对话框,调用方法,则将同时调用onCancel()和onDismiss()。
dismiss()

只有在调用onDismiss()之后才会被触发。

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