在窗口外触摸时如何取消以 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 为主题的对话框?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 iOS 中取消 UIScrollView 在三角形 UIView 外滚动?