How to cancel an Dialog themed like Activity when touched outside the window?
Asked Answered
A

17

50

I have an activity with a Dialog theme and I would like to close (finish) this activity when someone touches the screen anywhere outside this activity's window ? How can I do this ?

Alburg answered 10/1, 2011 at 18:46 Comment(2)
Android doesn't really have support for this. I'm not sure there's any way this can be done at all and wouldn't be natural for android users anyway. That's what the back button is for.Facilitate
For a dialog class is natural, SDK provides setCanceledOnTouchOutside method, so why it shouldn't be for a Dialog themed Activity ?Alburg
S
-2

If there's no API support, you should just use a FrameLayout to fill the screen, and manually build a pop-up. Then you can receive focus anywhere on the screen and show/hide views accordingly.

Snobbish answered 10/1, 2011 at 20:12 Comment(5)
I'm sorry but I'm not familiar with the FrameLayout, is there any example I could look at ?Alburg
There's an example here: curious-creature.org/2009/03/01/… Look how there's a small text box on top of the image... you can make that look like a popup and you still get all the notifications to the rest of the window.Snobbish
the answer of Gregory works for me, can't see how a FrameLayout would help in this case.Humphreys
the answer of @Harmony worked for me. because touch events does not pass to background activityScandic
I actually disagree with the many downvotes this answer got. It is the only reasonable alternative to no-api support. So could the no-voters please explain why this is 'so wrong' ?Presumptuous
P
101

Just to point out that there is a way to get dialog-like "touch outside to cancel" behaviour from an Activity themed as a dialog, though I've not fully investigated whether it has unwanted side effects.

Within your Activity's onCreate() method, before creating the view, you're going to set two flags on the window: One to make it 'non-modal', to allow views other than your activity's views to receive events. The second is to receive notification that one of those events has taken place, which will send you an ACTION_OUTSDIE move event.

If you set the theme on the activity to the dialog theme, you'll get the behaviour you want.

It looks something like this:

public class MyActivity extends Activity {

 @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Make us non-modal, so that others can receive touch events.
    getWindow().setFlags(LayoutParams.FLAG_NOT_TOUCH_MODAL, LayoutParams.FLAG_NOT_TOUCH_MODAL);

    // ...but notify us that it happened.
    getWindow().setFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);

    // Note that flag changes must happen *before* the content view is set.
    setContentView(R.layout.my_dialog_view);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    // If we've received a touch notification that the user has touched
    // outside the app, finish the activity.
    if (MotionEvent.ACTION_OUTSIDE == event.getAction()) {
      finish();
      return true;
    }

    // Delegate everything else to Activity.
    return super.onTouchEvent(event);
  }
}
Piping answered 29/4, 2011 at 11:6 Comment(6)
Hi this looks good, but in honeycomb if i have an activity over a listview and i click outside of the view, the touch event is passed to the listview even if i return true; Any idea how to resolve it ?Alburg
Two options come to mind, @alex - disable problematic UI elements during onPause/onResume, or do so before launching with startActivityForResult(), re-enabling those UI elements when the result code is returned.Piping
This is nice, however it does not swallow the outside touches. For instance my map behind this view is getting clicked when user closes the "dialog"Intumescence
WindowManager.LayoutParamsWeylin
@Alex: Try using setFinishOnTouchOutside(false);Hypertonic
thanks, it's work for me @MiteshShah ShahCutinize
H
77

I found an even simpler answer that has worked perfectly for me. If you're using an activity with the dialog theme then you can apply this.setFinishOnTouchOutside(true); to the activity's onCreate() method.

@Override
protected void onCreate(Bundle savedInstanceState) 
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_yoptions);
    this.setFinishOnTouchOutside(true);
}
Harmony answered 3/1, 2013 at 5:38 Comment(2)
The only thing that worked in my case, with an activity with Theme.Holo.Dialog style.Nishanishi
I was looking for the opposite behavior, and this answer works for that too. I wanted to not close a dialog-style activity, in this case a loading dialog, when the user taps outside it. Setting this to (false) worked.Bushranger
N
37

It's very simple, just set the property canceledOnTouchOutside = true. look at the example:

Dialog dialog = new Dialog(context)
dialog.setCanceledOnTouchOutside(true);
Nadler answered 21/7, 2011 at 18:5 Comment(3)
This only applies to actual dialogs though, not to regular activities using the dialog theme.Tapetum
To work with dialog theme call setFinishOnTouchOutside(true) on current object ie. this.setFinishOnTouchOutside(true);Pu
the question want dialog themed activity not dialog!Zonazonal
C
19

It is possible quite easily:

First define your own theme in style.xml:

<style name="DialogSlideAnim" parent="@android:style/Theme.Holo.Dialog">
    <item name="android:windowContentOverlay">@null</item>
    <item name="android:windowCloseOnTouchOutside">true</item>
</style>

Then in your manifest apply this theme to activity:

    <activity
        android:label="@string/app_name"
        android:name=".MiniModeActivity" 
        android:theme="@style/DialogSlideAnim" >
        <intent-filter >
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
Coprolalia answered 27/6, 2012 at 4:7 Comment(2)
the attribute is only available since api 11Circumfuse
how to access this one below api level 11Tours
A
8

A combination of Gregory and Matt's answers worked best for me (for Honeycomb and presumably others). This way, views outside will not get touch events when the user tries to touch-outside-cancel the dialog.

In the main Activity, create the touch interceptor in onCreate():

    touchInterceptor = new FrameLayout(this);
    touchInterceptor.setClickable(true); // otherwise clicks will fall through

In onPause() add it:

    if (touchInterceptor.getParent() == null) {
        rootViewGroup.addView(touchInterceptor);
    }

(rootViewGroup may have to be either a FrameLayout or a RelativeLayout. LinearLayout may not work.)

In onResume(), remove it:

    rootViewGroup.removeView(touchInterceptor);

Then, for the dialog-themed Activity, use the code Gregory offered (copied here for your convenience):

public class MyActivity extends Activity {   

 @Override   
  protected void onCreate(Bundle savedInstanceState) {   
    super.onCreate(savedInstanceState);   

    // Make us non-modal, so that others can receive touch events.   
    getWindow().setFlags(LayoutParams.FLAG_NOT_TOUCH_MODAL, LayoutParams.FLAG_NOT_TOUCH_MODAL);   

    // ...but notify us that it happened.   
    getWindow().setFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);   

    // Note that flag changes must happen *before* the content view is set.   
    setContentView(R.layout.my_dialog_view);   
  }   

  @Override   
  public boolean onTouchEvent(MotionEvent event) {   
    // If we've received a touch notification that the user has touched   
    // outside the app, finish the activity.   
    if (MotionEvent.ACTION_OUTSIDE == event.getAction()) {   
      finish();   
      return true;   
    }   

    // Delegate everything else to Activity.   
    return super.onTouchEvent(event);   
  }   
}   
Averroism answered 28/3, 2012 at 18:32 Comment(2)
I don't understand what rootViewGroup should be. WHat if I provide it with activity content like android.R.content?Bugbane
I'm not sure what you mean. But if the root content view you added via setContentView() is a subclass of ViewGroup, I believe you can use rootViewGroup = (ViewGroup) findViewById(android.R.id.content).Averroism
P
6

If using a dialog theme like android:theme="@style/Theme.AppCompat.Dialog" or any other dialog theme. On API 11 and after we can use

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        setFinishOnTouchOutside(false);
    }

call this inside onCreate of the activity.

Pareu answered 9/1, 2016 at 7:42 Comment(1)
Thank you for this. This helped me a lot +1Bucovina
W
4
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Rect dialogBounds = new Rect();
    getWindow().getDecorView().getHitRect(dialogBounds);

    if (!dialogBounds.contains((int) ev.getX(), (int) ev.getY())) {
        return true;
    }
    return super.dispatchTouchEvent(ev);
}

This code is solved my problem.

Wichman answered 23/11, 2013 at 10:9 Comment(0)
S
3

I couldn't get the top answer here to work on a Samsung tab running 3.1, so I did this:

@Override
public boolean onTouchEvent(MotionEvent event) {
    float x = event.getX();
    float y = event.getY();
    int xmargin = (ViewUtils.getScreenWidth() - Constants.PRODUCT_DIALOG_WIDTH) / 2;
    int ymargin = (ViewUtils.getScreenHeight() - Constants.PRODUCT_DIALOG_HEIGHT) / 2;

    if (
            x < xmargin                              || 
            x > ViewUtils.getScreenWidth() - xmargin ||
            y < ymargin                              || 
            y > ViewUtils.getScreenHeight() - ymargin
        ) {
            finish();
            return true;
    }
    return super.onTouchEvent(event);
}

You'll need to replace Constants.PRODUCT_DIALOG_WIDTH and Constants.PRODUCT_DIALOG_HEIGHT with the width/height of your dialog. Mine was used in a number of places so I made them constants.

You'll also need to implement your own method to get the the screen width and height, which you can find easily on this here site. Don't forget to account for the Android header in that!

It's kind of ugly, and I'm not proud, but it works.

Solitude answered 27/6, 2012 at 18:11 Comment(0)
M
3

You can reference the dialog.java code from android source:

public boolean onTouchEvent(MotionEvent event) {
    if (mCancelable && mCanceledOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(event)) {
        cancel();
        return true;
    }
    return false;
}

private boolean isOutOfBounds(MotionEvent event) {
    final int x = (int) event.getX();
    final int y = (int) event.getY();
    final int slop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop();
    final View decorView = getWindow().getDecorView();
    return (x < -slop) || (y < -slop) || (x > (decorView.getWidth()+slop)) || (y > (decorView.getHeight()+slop));
}

Just modify it to :

public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(event)) {
        finish();
        return true;
    }
    return false;
}

private boolean isOutOfBounds(MotionEvent event) {
    final int x = (int) event.getX();
    final int y = (int) event.getY();
    final int slop = ViewConfiguration.get(this).getScaledWindowTouchSlop();
    final View decorView = getWindow().getDecorView();
    return (x < -slop) || (y < -slop) || (x > (decorView.getWidth() + slop)) || (y > decorView.getHeight() + slop));
}

can solve your problem.

Mattos answered 31/3, 2013 at 2:29 Comment(0)
C
3

The only way I got this to work was

alert = new AlertDialog.Builder(this)....

        alert.setOnDismissListener(new DialogInterface.OnDismissListener() {
        @Override
        public void onDismiss(final DialogInterface arg0) {
            Log.i(APP_NAME, "in OnDismissListener");
            // removeDialog(R.layout.dialog3);
            alert.dismiss();
            finish();
        }

i.e. I had to put both dismiss and finish in explicitly, otherwise I ended up with a small white rectangle in the middle of the screen.

Celtic answered 5/4, 2014 at 12:3 Comment(0)
E
0

An Activity have dispatchTouchEvent use that

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // TODO Auto-generated method stub
    finish();
    return super.dispatchTouchEvent(ev);

}
Extenuate answered 9/9, 2014 at 7:6 Comment(0)
C
0

Just add this item to styles.xml:

<style name="alert_dialog" parent="android:Theme.Dialog">
    <item name="android:windowIsFloating">true</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowFullscreen">false</item>
    <item name="android:windowBackground">@color/float_transparent</item>
    <item name="android:windowAnimationStyle">@null</item>
    <item name="android:backgroundDimEnabled">true</item>
    <item name="android:backgroundDimAmount">0.4</item>
</style>

And in onCreate() and before setContentView:

setTheme(R.style.alert_dialog);
Cynthia answered 28/4, 2016 at 12:40 Comment(0)
F
0

Using method setFinishOnTouchOutside to enable/disable whether outside is touchable or not.

This is working for activity.

@Override
protected void onCreate(Bundle savedInstanceState) 
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_yoptions);
    /* your code here */

    // set outside touchable
    this.setFinishOnTouchOutside(true);
}
Fdic answered 8/2, 2018 at 15:57 Comment(0)
R
0

For those who want to not close the Dialog Application if touch is outside the Dialog. Add this line

this.setFinishOnTouchOutside(false);

It will not close the dialog box

Routh answered 29/10, 2018 at 5:42 Comment(0)
G
0

Just use this theme. Activity will be dismissed on touch outside.

<style name="DialogTheme" parent="Theme.MaterialComponents.DayNight.Dialog">
    <item name="android:windowIsTranslucent">true</item>
</style>
Gagnon answered 28/5, 2020 at 18:56 Comment(0)
O
0

Kotlin version worked for me

alert.setOnDismissListener(DialogInterface.OnDismissListener() {
    it.dismiss()
})
Onofredo answered 29/12, 2020 at 13:23 Comment(0)
S
-2

If there's no API support, you should just use a FrameLayout to fill the screen, and manually build a pop-up. Then you can receive focus anywhere on the screen and show/hide views accordingly.

Snobbish answered 10/1, 2011 at 20:12 Comment(5)
I'm sorry but I'm not familiar with the FrameLayout, is there any example I could look at ?Alburg
There's an example here: curious-creature.org/2009/03/01/… Look how there's a small text box on top of the image... you can make that look like a popup and you still get all the notifications to the rest of the window.Snobbish
the answer of Gregory works for me, can't see how a FrameLayout would help in this case.Humphreys
the answer of @Harmony worked for me. because touch events does not pass to background activityScandic
I actually disagree with the many downvotes this answer got. It is the only reasonable alternative to no-api support. So could the no-voters please explain why this is 'so wrong' ?Presumptuous

© 2022 - 2024 — McMap. All rights reserved.