Alex
Alex

Reputation: 2233

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

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 ?

Upvotes: 50

Views: 85301

Answers (17)

Dsda
Dsda

Reputation: 577

Kotlin version worked for me

alert.setOnDismissListener(DialogInterface.OnDismissListener() {
    it.dismiss()
})

Upvotes: 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>

Upvotes: 0

Smaran
Smaran

Reputation: 1651

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

Upvotes: 0

MAOXU
MAOXU

Reputation: 389

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

Upvotes: 0

Mohammad Zaer
Mohammad Zaer

Reputation: 696

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

Upvotes: 0

harsh_v
harsh_v

Reputation: 3269

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.

Upvotes: 6

sharath yadhav
sharath yadhav

Reputation: 546

An Activity have dispatchTouchEvent use that

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

}

Upvotes: 0

torwalker
torwalker

Reputation: 351

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.

Upvotes: 3

Mojiiz
Mojiiz

Reputation: 7958

@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.

Upvotes: 4

leon.liu
leon.liu

Reputation: 41

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.

Upvotes: 3

vabanagas
vabanagas

Reputation: 811

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

Upvotes: 77

user901309
user901309

Reputation:

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.

Upvotes: 3

avepr
avepr

Reputation: 1896

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>

Upvotes: 19

Ken
Ken

Reputation: 2964

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

Upvotes: 8

Luis Zandonadi
Luis Zandonadi

Reputation: 667

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

Dialog dialog = new Dialog(context)
dialog.setCanceledOnTouchOutside(true);

Upvotes: 37

Gregory Block
Gregory Block

Reputation: 1154

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

Upvotes: 101

Matt
Matt

Reputation: 3847

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.

Upvotes: -2

Related Questions