52

I would like to know how to solve a problem I've got.

I have a Dialog which pops up in an activity. The Dialog doesn't cover the whole screen, so the buttons from the activity still show. I can easily close the dialog when there is a touch outside the dialog's bounds with dialog.setCanceledOnTouchOutside(true);

However what I want to do is fire an event if a click is outside the Dialog's bounds (e.g if someone touches a button on the main Activity, it should close the Dialog and fire that event at the same time).

Sufian
  • 6,405
  • 16
  • 66
  • 120
fizo07
  • 615
  • 1
  • 7
  • 7

9 Answers9

85

When dialog.setCanceledOnTouchOutside(true); then you just override onCancel() like this:

dialog.setOnCancelListener(
        new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                //When you touch outside of dialog bounds, 
                //the dialog gets canceled and this method executes.
            }
        }
);

Type your code inside the onCancel() method so it runs when the dialog gets canceled.

Will Neithan
  • 1,190
  • 9
  • 12
45

It Works For me,,

        Window window = dialog.getWindow();
        window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);

        dialog.show();

See this http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_NOT_TOUCH_MODAL

Samir Mangroliya
  • 39,918
  • 16
  • 117
  • 134
  • This kinda works. Only that I can scroll listview but not click it. –  Oct 02 '19 at 20:21
4

In DialogFragment you can use AlertDialog and the answer of @silverTech:

override fun onDismiss(dialog: DialogInterface) {
    yourMethod()
    super.onDismiss(dialog)
}

or

override fun onCancel(dialog: DialogInterface) {
    yourMethod()
    super.onCancel(dialog)
}
CoolMind
  • 26,736
  • 15
  • 188
  • 224
  • 1
    Why do you recommend inserting logic before super.onDismiss()? And then why is logic after super.onCancel()? – AJW Jan 30 '20 at 18:34
  • 1
    @AJW, I opened `DialogFragment`. `onCancel` is empty, while `onDismiss` contains some logic about dismissing (closing) the dialog. I advise to write your logic first and then destroying methods (`super.onPause()`, `onDestroy`, `onSaveInstanceState` and so on). It would be better to finalize your variables before the system destroys objects. So, maybe better remove `super.onCancel(dialog)`. I rewrote `onCancel` after your comment, thanks. – CoolMind Jan 31 '20 at 14:00
2

You can use an OnCancelListener to fire an event when a click occurs outside a dialog:

dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
    @Override
    public void onCancel(DialogInterface dialog) {
        yourFunction();
    }
});
Abandoned Cart
  • 4,512
  • 1
  • 34
  • 41
1

If you are within custom dialog class, and wish to catch 'click outside' event - override cancel(). If you wish to catch 'dialog closed' event - override dismiss(). I recommend inserting logic BEFORE super.dismiss(). Kotlin example:

override fun dismiss() {
    Utils.hideKeyboard(mContext, window)
    super.dismiss()
}
SilverTech
  • 399
  • 4
  • 11
  • Not a very good answer. Meaning, it is not generic. I have to write new behaviour, rather than noticing old behaviour. In the case where I am making a general dialog utility, There is no way I can know what is behind the dialog. –  Oct 02 '19 at 20:25
  • @JaveneCPPMcGowan you can use callbacks from the Custom Dialogs to the views. – SilverTech Oct 15 '19 at 08:13
  • As I said, that is NEW BEHAVIOUR. –  Oct 15 '19 at 20:01
  • @SilverTech Why do you recommend inserting logic before super.dismiss()? – AJW Jan 30 '20 at 18:33
  • the other way around created a situation where only SOMETIMES the hideKeyboard func worked as expected. – SilverTech Feb 02 '20 at 08:27
1

Its available at com.google.android.material.bottomsheet.BottomSheetDialog

> // We treat the CoordinatorLayout as outside the dialog though it is technically inside
>     coordinator
>         .findViewById(R.id.touch_outside)
>         .setOnClickListener(
>             new View.OnClickListener() {
>               @Override
>               public void onClick(View view) {
>                 if (cancelable && isShowing() && shouldWindowCloseOnTouchOutside()) {
>                   cancel();
>                 }
>               }
>             });

so you can override the ClickListener at your BottomSheetDialog

class MyBottomDialogFragment : BottomSheetDialogFragment(){
 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = object : BottomSheetDialog(activity as Context, theme) {
            private var isDialogCancelable: Boolean = false
            private var isDialogCancelableOnTouchOutside: Boolean = false

            override fun onBackPressed() {
                handleBackPressed(this)
            }

            override fun setContentView(layoutResId: Int) {
                super.setContentView(layoutResId)
                setupTouchOutside()
            }

            override fun setContentView(view: View?) {
                super.setContentView(view)
                setupTouchOutside()
            }

            override fun setContentView(view: View?, params: ViewGroup.LayoutParams?) {
                super.setContentView(view, params)
                setupTouchOutside()
            }

            private fun setupTouchOutside() {
                val coordinator = findViewById<View>(com.google.android.material.R.id.coordinator) as CoordinatorLayout
                // We treat the CoordinatorLayout as outside the dialog though it is technically inside
                coordinator
                    .findViewById<View>(com.google.android.material.R.id.touch_outside)
                    .setOnClickListener {
                        if (isDialogCancelable && isShowing && isDialogCancelableOnTouchOutside) {
                            handleTouchOutside(this)
                        }
                    }
            }


            override fun setCancelable(cancelable: Boolean) {
                super.setCancelable(cancelable)
                isDialogCancelable = cancelable
            }

            override fun setCanceledOnTouchOutside(cancel: Boolean) {
                super.setCanceledOnTouchOutside(cancel)
                isDialogCancelableOnTouchOutside = cancel
            }
        }
        dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
        dialog.setCanceledOnTouchOutside(true)
        return dialog
    }

  protected open fun handleBackPressed(dialog: BottomSheetDialog) {
        /* do what you want but after that call dialog.cancel() or dialog.dismiss() */
    }

    protected open fun handleTouchOutside(dialog: BottomSheetDialog) {
        /* do what you want but after that call dialog.cancel() or dialog.dismiss() */        
    }
}
NickUnuchek
  • 11,794
  • 12
  • 98
  • 138
0

Returning to previous screen with back button command.

Use the override method onCancel in your class that extends DialogFragment . . .

@Override
    public void onCancel(@NonNull DialogInterface dialog)
    {
        super.onCancel(dialog);
        getParentFragment().getActivity().onBackPressed();
    }
tzg
  • 616
  • 1
  • 8
  • 17
0

I found all the other answers quite lengthy and complicated, so I used this approach:

Step 1: Create an ID for the outside container of your element for which you want to generate a click outside event.

In my case, it is a Linear Layout for which I've given id as 'outsideContainer'

Step 2: Set an onTouchListener for that outside container which will simply act as a click outside event for your inner elements!

outsideContainer.setOnTouchListener(new View.OnTouchListener() {
                                            @Override
                                            public boolean onTouch(View v, MotionEvent event) {
                                                // perform your intended action for click outside here
                                                Toast.makeText(YourActivity.this, "Clicked outside!", Toast.LENGTH_SHORT).show();
                                                return false;
                                            }
                                        }
    );
Akshay Chavan
  • 185
  • 1
  • 6
0

@WillNeithan answer in Kotlin:

val alertDialog = AlertDialog.Builder(this).create()
// ... complete setting up alertDialog
    
alertDialog.setOnCancelListener(
    DialogInterface.OnCancelListener {
        println("User Tapped Outside to Dismiss alertDialog")                 
    }
)
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256