Android 扩大 View 的点击区域
有时候,按照视觉图做出来效果后,发现点击区域过小,不好点击,用户体验肯定不好。扩大视图,就会导致整个视觉图变得不好看。那么有没有什么办法在不改变视图大小的前提下扩大点击区域呢?
答案是有!
能够解决这个问题的前提你要对 View 的事件分发机制有一定的了解。
下面我将简单介绍一下View 的事件分发机制,方便大家理解后面的解决办法。
为了更清楚的说明整个机制,采用如下的视图来说明点击的事件分发机制。下图是一个 FrameLayout (ViewGroup) 里面包含着一个 ImageView (View)。
先自定义一个 MyFrameLayout,继承FrameLayout,并实现两个点击相关的接口;具体代码如下:
- public class MyFrameLayout extends FrameLayout implements OnClickListener, OnTouchListener {
- private static final String TAG = "Event";
- public MyFrameLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- Log.d(TAG, "MyFrameLayout init");
- setOnClickListener(this);
- setOnTouchListener(this);
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- Log.d(TAG, "MyFrameLayout dispatchTouchEvent " + event.getAction());
- return super.dispatchTouchEvent(event);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.d(TAG, "MyFrameLayout onTouchEvent " + event.getAction() );
- return super.onTouchEvent(event);
- }
- @Override
- public void onClick(View view) {
- Log.d(TAG, "MyFrameLayout onClick");
- }
- @Override
- public boolean onTouch(View view, MotionEvent event) {
- Log.d(TAG, "MyFrameLayout onTouch " + event.getAction());
- return true;
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- Log.d(TAG, "MyFrameLayout onInterceptTouchEvent " + ev.getAction());
- return super.onInterceptTouchEvent(ev);
- }
- }
接着,对于 ImageView 也做类似的操作,具体代码如下:
- public class MyImageView extends ImageView implements OnClickListener, OnTouchListener {
- private static final String TAG = "Event";
- public MyImageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- Log.d(TAG, "MyImageView init");
- setOnClickListener(this);
- setOnTouchListener(this);
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- Log.d(TAG, "MyImageView dispatchTouchEvent "+ event.getAction());
- return super.dispatchTouchEvent(event);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.d(TAG, "MyImageView onTouchEvent "+ event.getAction());
- return super.onTouchEvent(event);
- }
- @Override
- public boolean onTouch(View arg0, MotionEvent arg1) {
- Log.d(TAG, "MyImageView onTouch " + arg1.getAction());
- return false;
- }
- @Override
- public void onClick(View arg0) {
- Log.d(TAG, "MyImageView onClick");
- }
- }
这里要说明的是,只有ViewGroup才有 onInterceptTouchEvent 方法的,普通的 View 是没有的,它是不能对事件进行拦截的。
那这时候,如果我们点击里面的 ImageView,会有怎样的输出呢?结果如下图。
那如果点击外层呢?
0,1,2分别是代表 ACTION_DOWN,ACTION_UP,ACTION_MOVE;从中也可以看出一个点击动作包含一个Down,一个Up,还有多个Move操作。
再来看一段源码:
- public boolean dispatchTouchEvent(MotionEvent ev){
- boolean consume = false;
- if(onInterceptTouchEvent(ev)){
- consume = onTouchEvent(ev);
- } else{
- consume = child.dispatchTouchEvent(ev);
- }
- return consume;
- }
上述的代码把三者的关系说得很清楚了,对于一个对于一个 ViewGroup 来说,点击事件产生后,首先会传递给它,这时候会调用 dispatchTouchEvent,如果这个 ViewGroup 的 onInterceptTouchEvent 返回 true ,则表示它要拦截该事件,也就会交给它的 onTouchEvent 来进行处理。如果这个 ViewGroup 的 onInterceptTouchEvent 返回 false 则会传给子元素,子元素的 dispatchTouchEvent 就会被调用,如此反复循环。这与上面一张图打出的结果是一致的。
这里还有说明的是,如果代码设置了 OnTouchListener,那么就会先调用 onTouch 方法,然后在调用 onTouchEvent。OnClickListener 是优先级最低的,所以最后才会调用 onClick。
因此,从第二张结果图也可以看出,当存在 onTouch 之后,onTouchEvent 和 onClick 两个方法都不会在调用了。
相信到这里,大家对于View的事件分发机制有一定的了解了。
这里回到开头提的那个问题,那么有什么办法可以扩大 View 的点击区域呢?
答案:在父 View 设置 OnTouchListener 对点击事件进行拦截,通过判断点击的位置,来决定是相应子 View 的事件,还是父 View 的事件。
具体实现代码如下:
- public class TouchFactory {
- /** 扩展垂直方向点击区域尺寸 */
- private static final int EXT_V_SIZE = 200;
- public static View.OnTouchListener creatTouchListener(){
- return new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (expendTouchSize(v, event)) {
- return true;
- }
- return false;
- }
- };
- }
- public static boolean expendTouchSize(View root, MotionEvent event) {
- if (root instanceof MyFrameLayout) {
- ImageView view = ((MyFrameLayout) root).getMyImageView();
- if (view != null && view.getVisibility() == View.VISIBLE) {
- Rect touchRect = new Rect();
- view.getGlobalVisibleRect(touchRect);
- int action = event.getAction();
- float x = event.getRawX();
- float y = event.getRawY();
- if ((y >= touchRect.top - EXT_V_SIZE) && (y <= touchRect.bottom + EXT_V_SIZE)) {
- if (x >= touchRect.left) {
- if (action == MotionEvent.ACTION_UP) {
- Toast.makeText(view.getContext(), "touch", Toast.LENGTH_SHORT).show();
- }
- return true;
- }
- }
- }
- }
- return false;
- }
- }
TouchFactory 对点击事件进行了封装,并通过对点击区域的判断,来决定要不要拦截点击事件。
下面是 MyFrameLayout 的具体实现。由于是一个自定义 view, 因此,变量 myImageView 是一定为空的,所以要对其进行赋值。
- public class MyFrameLayout extends FrameLayout {
- private static final String TAG = "Event";
- private MyImageView myImageView;
- public MyFrameLayout(Context context) {
- this(context, null);
- }
- public MyFrameLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
- public MyFrameLayout(Context context, AttributeSet attrs, int def) {
- super(context, attrs, def);
- init();
- }
- public void init() {
- this.setOnTouchListener(TouchFactory.creatTouchListener());
- }
- public MyImageView getMyImageView() {
- if (myImageView == null) {
- myImageView = findViewById(R.id.mImage);
- }
- return myImageView;
- }
- }
注意事项:当对子 View 设置 OnClickListener,点击区域刚好是子 View 内部的时候,就会消耗此事见,父 View 的拦截处理就无效了,因此,一旦选择拦截来扩大点击区域,就不要再去子 View 设置点击回调来消耗点击事件了。
Android 扩大 View 的点击区域的更多相关文章
- Android 自定义View——自定义点击事件
每个人手机上都有通讯录,这是毫无疑问的,我们通讯录上有一个控件,在通讯录的最左边有一列从”#”到”Z”的字母,我们通过滑动或点击指定的字母来确定联系人的位置,进而找到联系人.我们这一节就通过开发这个控 ...
- android 扩大view的响应区域
1.Android提供TouchDelegate帮助实现扩大一个很小的view的点击区域 例如:https://developer.android.com/training/gestures/view ...
- Android上滑手势触发和不增加布局层级扩大点击区域
最近项目中需要实现手势上滑或者点击滑出界面的效果,实现上是利用GestureDetector,然后在onFling中判断,但遇到一个问题:手势上滑是针对整个布局的,但如果有对单独的View设置点击监听 ...
- Android不规则点击区域详解
Android不规则点击区域详解 摘要 今天要和大家分享的是Android不规则点击区域,准确说是在视觉上不规则的图像点击响应区域分发. 其实这个问题比较简单,对于很多人来说根本不值得做为一篇博文写出 ...
- android View的点击无效的原因
点击事件不生效,原来是因为我在里面的 ImageView中添加了 android:clickable="true". 解决办法:删掉ImageView中的android:click ...
- Android 自定义View可拖动移动位置及边缘拉伸放大缩小
一.首先说一下定义这样一个View有什么用?在一些app中,需要设置头像,而用户选择的图片可能是使用摄像头拍摄,也可能是选择的相册里面的图片,总之,这样的图片大小不一,就比如在使用某个聊天软件的时候, ...
- Android自定义View(CustomCalendar-定制日历控件)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeas ...
- Android 开发 View的API 转载
转载地址:https://blog.csdn.net/lemonrabbit1987/article/details/47704679 View类代表用户界面组件的基本构建块.一个View占据屏幕上的 ...
- [Android UI]View基础知识
一.简介 在安卓中,View代表视图,是安卓中十分重要的一个概念,重要程度不亚于四大组件,用户每时每刻都在与View打交道,包括展示数据.事件传递等.因此,熟练掌握View的应用以及原理是Androi ...
随机推荐
- 常用Oracle进程资源查询语句(运维必看)
(一)根据程序名称查找相关信息select A.process,B.spid,A.sid,A.serial#,A.sql_address,A.username,A.program,A.status,A ...
- sql语句转为Model
在跟数据库打交道的时候,有一个常用的应用,就是把数据库中的表转为程序中的对象,也就是说表中的列转为对象的属性.对于字段比较少的,我们可以直接复制过去改,但是字段数比较多的时候,借助工具类实现比较方便而 ...
- AndroidStudio Frameworks detected: Android framework is detected in the project Configure
出现这个问题应该是文件没有用正确的方式打开. 遇到这种情况,就要去检查下载的这个包的结构. 我的这个文件明显真正的是下面这个文件夹,如果把整个当做一个android文件打开会导致文件结 ...
- java 向上转型与向下转型
转型是在继承的基础上而言的,继承是面向对象语言中,代码复用的一种机制,通过继承,子类可以复用父类的功能,如果父类不能满足当前子类的需求,则子类可以重写父类中的方法来加以扩展. 向上转型:子类引用的对象 ...
- Java集合框架(二)
原文 http://www.jianshu.com/p/2070cb32accb List接口 查阅API,看 List 的介绍.有序的 collection (也称为序列).此接口的用户可以对列表 ...
- Python sort后赋值 操作陷阱
x=[1,4,2,0] # 错误的方式,因为sort没有返回值 y=x.sort() type (y) #NoneType #正确的方式 x.sort() y=x[:]
- 笔记:XML-解析文档-流机制解析器(SAX、StAX)
DOM 解析器完整的读入XML文档,然后将其转换成一个树型的数据结构,对于大多数应用,DOM 都运行很好,但是,如果文档很大,并且处理算法又非常简单,可以在运行时解析节点,而不必看到完整的树形结构,那 ...
- Bitmap的加载与缓存
Android系统中图片一般用Bitmap对象表示,它支持png,jpg等常见格式.通常情况下图片的体积都比较大,单个应用允许使用的内存又是有限的,所以我们需要采取一些手段减少内存占用并提高加载速度. ...
- 数据库 --> SQL Server 和 Oracle 以及 MySQL 区别
SQL Server 和 Oracle 以及 MySQL 区别 三者是目前市场占有率最高(依安装量而非收入)的关系数据库,而且很有代表性.排行第四的DB2(属IBM公司),与Oracle的定位和架构非 ...
- Item 15: 只要有可能,就使用constexpr
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 如果说C++11中有什么新东西能拿"最佳困惑奖" ...