在窗口外触摸时如何取消以 Activity 为主题的对话框?

Posted

技术标签:

【中文标题】在窗口外触摸时如何取消以 Activity 为主题的对话框?【英文标题】:How to cancel an Dialog themed like Activity when touched outside the window? 【发布时间】:2011-06-06 17:17:42 【问题描述】:

我有一个 Dialog 主题的活动,当有人触摸此活动窗口外的任何位置的屏幕时,我想关闭(完成)此活动?我该怎么做?

【问题讨论】:

android 并不真正支持这一点。我不确定有什么办法可以做到这一点,而且无论如何对于安卓用户来说都是不自然的。这就是后退按钮的用途。 对于一个对话框类是自然的,SDK提供了setCanceledOnTouchOutside方法,那为什么不应该是一个Dialog主题的Activity呢? 【参考方案1】:

只是要指出 有一种方法可以从以对话框为主题的 Activity 中获得类似于对话框的“触摸外部以取消”行为,尽管我还没有完全调查它是否有不需要的副作用。

在您的 Activity 的 onCreate() 方法中,在创建视图之前,您将在窗口上设置两个标志:一个使其成为“非模态”,允许您的 Activity 视图以外的视图接收事件。第二个是接收其中一个事件发生的通知,这将向您发送一个 ACTION_OUTSDIE 移动事件。

如果您将 Activity 上的主题设置为对话框主题,您将获得所需的行为。

看起来像这样:

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

【讨论】:

嗨,这看起来不错,但是在蜂窝中,如果我在列表视图上有一个活动并且我在视图之外单击,即使我返回 true,触摸事件也会传递给列表视图;知道如何解决吗? 我想到了两个选项,@alex - 在 onPause/onResume 期间禁用有问题的 UI 元素,或者在使用 startActivityForResult() 启动之前这样做,在返回结果代码时重新启用这些 UI 元素。 这很好,但它不会吞噬外部的触感。例如,当用户关闭“对话框”时,我在此视图后面的地图被点击了 WindowManager.LayoutParams @Alex:尝试使用setFinishOnTouchOutside(false);【参考方案2】:

我找到了一个更简单的答案,对我来说非常有效。如果您正在使用带有对话框主题的活动,则可以将this.setFinishOnTouchOutside(true); 应用于活动的 onCreate() 方法。

@Override
protected void onCreate(Bundle savedInstanceState) 

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_yoptions);
    this.setFinishOnTouchOutside(true);

【讨论】:

对我来说唯一有效的方法是使用 Theme.Holo.Dialog 样式的活动。 我在寻找相反的行为,这个答案也适用。我想关闭一个对话框样式的活动,在这种情况下是一个加载对话框,当用户在它外面点击时。将此设置为 (false) 有效。【参考方案3】:

很简单,设置属性canceledOnTouchOutside = true即可。看例子:

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

【讨论】:

这仅适用于实际对话框,不适用于使用对话框主题的常规活动。 在当前对象上使用对话框主题调用setFinishOnTouchOutside(true),即。 this.setFinishOnTouchOutside(true); 问题想要对话主题活动而不是对话!【参考方案4】:

这很容易:

首先在style.xml中定义自己的主题:

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

然后在您的清单中将此主题应用于活动:

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

【讨论】:

该属性仅在 api 11 后可用 如何在 api 11 级以下访问这个【参考方案5】:

Gregory 和 Matt 的答案的组合对我最有效(对于 Honeycomb 和大概其他人)。这样,当用户尝试 touch-outside-cancel 对话框时,外部视图将不会收到触摸事件。

在主Activity中,在onCreate()中创建触摸拦截器:

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

在 onPause() 中添加:

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

(rootViewGroup 可能必须是 FrameLayout 或 RelativeLayout。LinearLayout 可能不起作用。)

在 onResume() 中,删除它:

    rootViewGroup.removeView(touchInterceptor);

然后,对于以对话为主题的 Activity,使用 Gregory 提供的代码(为方便起见,在此处复制):

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

【讨论】:

我不明白 rootViewGroup 应该是什么。如果我向它提供 android.R.content 之类的活动内容会怎样? 我不确定你的意思。但是如果你通过 setContentView() 添加的根内容视图是 ViewGroup 的子类,我相信你可以使用 rootViewGroup = (ViewGroup) findViewById(android.R.id.content)。【参考方案6】:

如果使用android:theme="@style/Theme.AppCompat.Dialog" 之类的对话框主题或任何其他对话框主题。 在 API 11 及之后我们可以使用

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

在活动的onCreate 内调用它。

【讨论】:

谢谢你。这对我帮助很大 +1【参考方案7】:
@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);

这段代码解决了我的问题。

【讨论】:

【参考方案8】:

我无法在运行 3.1 的三星标签上获得最佳答案,所以我这样做了:

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

您需要将 Constants.PRODUCT_DIALOG_WIDTH 和 Constants.PRODUCT_DIALOG_HEIGHT 替换为对话框的宽度/高度。我的在很多地方都使用过,所以我将它们设为常量。

您还需要实现自己的方法来获取屏幕宽度和高度,您可以在此站点上轻松找到它们。不要忘记在其中考虑 Android 标头!

这有点难看,我并不自豪,但它确实有效。

【讨论】:

【参考方案9】:

你可以参考android源码中的dialog.java代码:

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

只需修改为:

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

可以解决你的问题。

【讨论】:

【参考方案10】:

我让它工作的唯一方法是

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

即我必须明确地把dismiss和finish都放进去,否则我最终会在屏幕中间出现一个白色的小矩形。

【讨论】:

【参考方案11】:

一个 Activity 有 dispatchTouchEvent 使用它

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


【讨论】:

【参考方案12】:

只需将此项目添加到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>

onCreate()setContentView之前:

setTheme(R.style.alert_dialog);

【讨论】:

【参考方案13】:

使用setFinishOnTouchOutside方法来启用/禁用外部是否可触摸。

这对活动有效

@Override
protected void onCreate(Bundle savedInstanceState) 

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_yoptions);
    /* your code here */

    // set outside touchable
    this.setFinishOnTouchOutside(true);

【讨论】:

【参考方案14】:

如果触摸在对话框之外,不希望关闭对话框应用程序。 添加这一行

this.setFinishOnTouchOutside(false);

不会关闭对话框

【讨论】:

【参考方案15】:

只需使用这个主题。活动将在触摸外部时关闭。

<style name="DialogTheme" parent="Theme.MaterialComponents.DayNight.Dialog">
    <item name="android:windowIsTranslucent">true</item>
</style>

【讨论】:

【参考方案16】:

Kotlin 版本适合我

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

【讨论】:

【参考方案17】:

如果没有 API 支持,您应该只使用 FrameLayout 来填充屏幕,然后手动构建一个弹出窗口。然后您可以在屏幕上的任何位置接收焦点并相应地显示/隐藏视图。

【讨论】:

很抱歉,我对 FrameLayout 不熟悉,有什么可以看的例子吗? 这里有一个例子:curious-creature.org/2009/03/01/… 看看图片顶部有一个小文本框......你可以让它看起来像一个弹出窗口,你仍然可以收到所有通知给其余的人窗户。 Gregory 的答案对我有用,在这种情况下看不到 FrameLayout 会有什么帮助。 @vabanagas 的答案对我有用。因为触摸事件不会传递给后台活动 我实际上不同意这个答案得到的许多反对意见。它是无 api 支持的唯一合理替代方案。那么没有投票者可以解释为什么这是“如此错误”吗?

以上是关于在窗口外触摸时如何取消以 Activity 为主题的对话框?的主要内容,如果未能解决你的问题,请参考以下文章

允许触摸表格单元格以取消其他当前触摸

如何避免 Touches 取消事件?

如何在 iOS 中取消 UIScrollView 在三角形 UIView 外滚动?

在文本字段外触摸时如何关闭键盘?

android 如何实现点击activity窗口以外的地方关闭或隐藏窗口?

触摸时取消 UIButton 触摸