Activity设置Dialog属性问题总结
2016-04-08 11:32
681 查看
一、问题背景
在开发过程中,我们有时会通过Activity实现弹窗效果,就是所谓的伪弹窗。实现很简单,在AndroidMenifest.xml中将对应的Activity增加android:theme="@android:style/Theme.Dialog"属性即可(也可以自定义弹窗的样式)。对于弹窗,点击其周围的空白区域,正常情况下弹窗都会自动消失,然而,在2.x手机伪弹窗却不会自动消失。
二、问题分析
根据问题现象,首先需要通过源码分析Activity的onTouchEvent()做什么处理。
先分析api-11的源码如下:
根据上面的源码,activity接收到touch事件时,会调用mWindow.shouldCloseOnTouch(),返回true则finish(),所以点击空白区域会关闭就是因为这里执行了finish()。在方法mWindow.shouldCloseOnTouch()中,会通过isOutOfBounds(context,event)判断是否点击了空白区域,另外还会判断变量mCloseOnTouchOutside是否为true,初始值是false,在Window中追查该变量,发现可以通过方法setCloseOnTouchOutside(booleanclose),修改该变量。在Activity中追查是否调用了mWindow.setCloseOnTouchOutside(),发现有这么一个方法:
注释解释得很清楚了,另外该方法在api11才新增的。实际上,我们并没有调用Activity.setFinishOnTouchOutside(),但依然可以点击空白区域消失(mCloseOnTouchOutside初始值是false),因此继续追查在哪个地方修改了mCloseOnTouchOutside为true。在类Activity中并没有自身调用到该接口setFinishOnTouchOutside(),接着重写了该接口并断点看看谁调用了,实际上也没有,因此就有可能是类Window自身调用并修改了mCloseOnTouchOutside。通过Activity.getWindow()发现对象mWindow其实是PhoneWindow,见以下的方法:
通过上面的源码分析,已经可以清楚为何点击空白区域会消失的原因了,接着分析api-10的源码。
所以,api-11以前的版本Activity对touch事件是不做任何处理的。
三、问题处理
针对该问题,如果点击Activity伪弹窗的空白区域需要关闭Activity,对于api11及以后的版本,只需要在Activity创建时调用setFinishOnTouchOutside(true)就可以了;对于之前的版本就需要重写方法onTouchEvent()了。对此有两者方案,一种就是直接采取api11源码的处理方式,如下:
另一种就是在Activity执行onCreate()时,设置如下的窗口属性:
上面的属性表示点击伪弹窗外部空白区域时,不截取触摸事件,同时可以监听到点击了外部区域,即会回调到onTouchEvent(MotionEventevent),其中event是一个ACTION_OUTSIDE事件。因此,重写onTouchEvent()如下:
然而,该处理方法有个问题就是,点击外部区域时,弹窗后面的控件也会接收到触摸事件。
在开发过程中,我们有时会通过Activity实现弹窗效果,就是所谓的伪弹窗。实现很简单,在AndroidMenifest.xml中将对应的Activity增加android:theme="@android:style/Theme.Dialog"属性即可(也可以自定义弹窗的样式)。对于弹窗,点击其周围的空白区域,正常情况下弹窗都会自动消失,然而,在2.x手机伪弹窗却不会自动消失。
二、问题分析
根据问题现象,首先需要通过源码分析Activity的onTouchEvent()做什么处理。
先分析api-11的源码如下:
class Activity: public boolean onTouchEvent(MotionEvent event) { if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; } class Window: public boolean shouldCloseOnTouch(Context context, MotionEvent event) { if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event) && peekDecorView() != null) { return true; } return false; } private boolean isOutOfBounds(Context context, MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop(); final View decorView = getDecorView(); return (x < -slop) || (y < -slop) || (x > (decorView.getWidth() + slop)) || (y > (decorView.getHeight() + slop));
根据上面的源码,activity接收到touch事件时,会调用mWindow.shouldCloseOnTouch(),返回true则finish(),所以点击空白区域会关闭就是因为这里执行了finish()。在方法mWindow.shouldCloseOnTouch()中,会通过isOutOfBounds(context,event)判断是否点击了空白区域,另外还会判断变量mCloseOnTouchOutside是否为true,初始值是false,在Window中追查该变量,发现可以通过方法setCloseOnTouchOutside(booleanclose),修改该变量。在Activity中追查是否调用了mWindow.setCloseOnTouchOutside(),发现有这么一个方法:
class Activity: /** * Sets whether this activity is finished when touched outside its window's * bounds. */ public void setFinishOnTouchOutside(boolean finish) { mWindow.setCloseOnTouchOutside(finish); }
注释解释得很清楚了,另外该方法在api11才新增的。实际上,我们并没有调用Activity.setFinishOnTouchOutside(),但依然可以点击空白区域消失(mCloseOnTouchOutside初始值是false),因此继续追查在哪个地方修改了mCloseOnTouchOutside为true。在类Activity中并没有自身调用到该接口setFinishOnTouchOutside(),接着重写了该接口并断点看看谁调用了,实际上也没有,因此就有可能是类Window自身调用并修改了mCloseOnTouchOutside。通过Activity.getWindow()发现对象mWindow其实是PhoneWindow,见以下的方法:
class PhoneWindow extends Window: protected ViewGroup generateLayout(DecorView decor) { … if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion>= android.os.Build.VERSION_CODES.HONEYCOMB) { if (a.getBoolean( com.android.internal.R.styleable.Window_windowCloseOnTouchOutside, false)) { setCloseOnTouchOutsideIfNotSet(true); } } … } class Window: public void setCloseOnTouchOutsideIfNotSet(boolean close) { if (!mSetCloseOnTouchOutside) { CloseOnTouchOutside = close; mSetCloseOnTouchOutside = true; } }
通过上面的源码分析,已经可以清楚为何点击空白区域会消失的原因了,接着分析api-10的源码。
class Activity: public boolean onTouchEvent(MotionEvent event) { return false; }
所以,api-11以前的版本Activity对touch事件是不做任何处理的。
三、问题处理
针对该问题,如果点击Activity伪弹窗的空白区域需要关闭Activity,对于api11及以后的版本,只需要在Activity创建时调用setFinishOnTouchOutside(true)就可以了;对于之前的版本就需要重写方法onTouchEvent()了。对此有两者方案,一种就是直接采取api11源码的处理方式,如下:
class Activity: private boolean mIsFinishOnTouchOutside = true; @Override public boolean onTouchEvent(MotionEvent event) { if (mIsFinishOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(this, event)) { finish(); return true; } return super.onTouchEvent(event); } private boolean isOutOfBounds(Activity context, MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop(); final View decorView = context.getWindow().getDecorView(); return (x < -slop) || (y < -slop)|| (x > (decorView.getWidth() + slop))|| (y > (decorView.getHeight() + slop)); } public void setFinishOnTouchOutside(boolean finish) { mIsFinishOnTouchOutside = finish; }
另一种就是在Activity执行onCreate()时,设置如下的窗口属性:
getWindow().setFlags(LayoutParams.FLAG_NOT_TOUCH_MODAL, LayoutParams.FLAG_NOT_TOUCH_MODAL); getWindow().setFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
上面的属性表示点击伪弹窗外部空白区域时,不截取触摸事件,同时可以监听到点击了外部区域,即会回调到onTouchEvent(MotionEventevent),其中event是一个ACTION_OUTSIDE事件。因此,重写onTouchEvent()如下:
@Override public boolean onTouchEvent(MotionEvent event) { if (MotionEvent.ACTION_OUTSIDE == event.getAction()) { finish(); return true; } return super.onTouchEvent(event); }
然而,该处理方法有个问题就是,点击外部区域时,弹窗后面的控件也会接收到触摸事件。
相关文章推荐
- 面向对象编程要点
- djangosnippets: Diagram of your database structure
- 轻量级计算点击UILabel中的文字位置
- 汉堡包1.0
- 递归打印多维数组
- 汉堡博客
- webstrom快捷键
- 用PS简单制作促销海报的放射性渐变背景
- leetcode---Merge Sorted Array
- mybatis 中的批量添加、删除、修改
- leetcode182-Duplicate Emails(找出出现重复的数据)
- iOS中定时任务的三种方法
- shell变量$#,$@,$0,$1,$2的含义
- #!/bin/sh 和 #!/bin/bash 区别 2013年3月14日
- Tomcat – java.lang.OutOfMemoryError: PermGen space
- JavaScript判断数据类型总结
- 使用telnet玩一下http
- python if __name__ == ' __main__'
- ldconfig命令用法笔记
- Default 方法