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 ?

Alex
  • 2,213
  • 4
  • 32
  • 44
  • 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. – Falmarri Jan 10 '11 at 18:54
  • 2
    For a dialog class is natural, SDK provides setCanceledOnTouchOutside method, so why it shouldn't be for a Dialog themed Activity ? – Alex Jan 10 '11 at 18:58

17 Answers17

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);
  }
}
Gregory Block
  • 1,154
  • 1
  • 7
  • 6
  • 5
    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 ? – Alex Jul 12 '11 at 11:33
  • 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. – Gregory Block Jul 26 '11 at 07:30
  • 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" – Aiden Fry Sep 17 '13 at 11:11
  • 5
    WindowManager.LayoutParams – M. Usman Khan Jan 26 '15 at 13:26
  • 9
    @Alex: Try using `setFinishOnTouchOutside(false);` – Mitesh Shah Apr 20 '16 at 10:32
  • thanks, it's work for me @MiteshShah Shah – MD.Riyaz Aug 06 '21 at 04:56
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);
}
vabanagas
  • 811
  • 7
  • 6
  • The only thing that worked in my case, with an activity with Theme.Holo.Dialog style. – XMight Jan 28 '13 at 15:18
  • 4
    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. – Phil Ringsmuth Jul 24 '14 at 16:45
37

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

Dialog dialog = new Dialog(context)
dialog.setCanceledOnTouchOutside(true);
axel22
  • 32,045
  • 9
  • 125
  • 137
Luis Zandonadi
  • 667
  • 7
  • 4
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>
avepr
  • 1,865
  • 14
  • 16
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);   
  }   
}   
Ken
  • 2,918
  • 1
  • 25
  • 31
  • I don't understand what rootViewGroup should be. WHat if I provide it with activity content like android.R.content? – Jacek Kwiecień Apr 17 '14 at 07:55
  • 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). – Ken Apr 21 '14 at 23:40
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.

harsh_v
  • 3,193
  • 3
  • 34
  • 53
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.

Mojiiz
  • 7,898
  • 6
  • 26
  • 25
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.

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.

leon.liu
  • 41
  • 5
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.

torwalker
  • 351
  • 2
  • 5
0

An Activity have dispatchTouchEvent use that

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

}
sharath yadhav
  • 546
  • 5
  • 5
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);
Gustavo Morales
  • 2,614
  • 9
  • 29
  • 37
Mohammad Zaer
  • 638
  • 7
  • 9
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);
}
MAOXU
  • 329
  • 2
  • 3
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

Smaran
  • 1,651
  • 2
  • 11
  • 15
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>
0

Kotlin version worked for me

alert.setOnDismissListener(DialogInterface.OnDismissListener() {
    it.dismiss()
})
Eugene
  • 547
  • 4
  • 11
-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.

Matt
  • 3,837
  • 26
  • 29
  • I'm sorry but I'm not familiar with the FrameLayout, is there any example I could look at ? – Alex Jan 10 '11 at 20:34
  • There's an example here: http://www.curious-creature.org/2009/03/01/android-layout-tricks-3-optimize-part-1/ 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. – Matt Jan 11 '11 at 16:55
  • the answer of Gregory works for me, can't see how a FrameLayout would help in this case. – Tosa Aug 25 '11 at 09:50
  • the answer of @vabanagas worked for me. because touch events does not pass to background activity – moallemi Mar 10 '14 at 06:24
  • 2
    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' ? –  Apr 19 '14 at 10:28