有时候,按照视觉图做出来效果后,发现点击区域过小,不好点击,用户体验肯定不好。扩大视图,就会导致整个视觉图变得不好看。那么有没有什么办法在不改变视图大小的前提下扩大点击区域呢?

答案是有!

能够解决这个问题的前提你要对 View 的事件分发机制有一定的了解。

下面我将简单介绍一下View 的事件分发机制,方便大家理解后面的解决办法。

为了更清楚的说明整个机制,采用如下的视图来说明点击的事件分发机制。下图是一个 FrameLayout (ViewGroup) 里面包含着一个 ImageView (View)。

先自定义一个 MyFrameLayout,继承FrameLayout,并实现两个点击相关的接口;具体代码如下:

  1. public class MyFrameLayout extends FrameLayout implements OnClickListener, OnTouchListener {
  2.  
  3. private static final String TAG = "Event";
  4.  
  5. public MyFrameLayout(Context context, AttributeSet attrs) {
  6. super(context, attrs);
  7. Log.d(TAG, "MyFrameLayout init");
  8. setOnClickListener(this);
  9. setOnTouchListener(this);
  10. }
  11.  
  12. @Override
  13. public boolean dispatchTouchEvent(MotionEvent event) {
  14. Log.d(TAG, "MyFrameLayout dispatchTouchEvent " + event.getAction());
  15. return super.dispatchTouchEvent(event);
  16. }
  17.  
  18. @Override
  19. public boolean onTouchEvent(MotionEvent event) {
  20. Log.d(TAG, "MyFrameLayout onTouchEvent " + event.getAction() );
  21. return super.onTouchEvent(event);
  22. }
  23.  
  24. @Override
  25. public void onClick(View view) {
  26. Log.d(TAG, "MyFrameLayout onClick");
  27. }
  28.  
  29. @Override
  30. public boolean onTouch(View view, MotionEvent event) {
  31. Log.d(TAG, "MyFrameLayout onTouch " + event.getAction());
  32. return true;
  33. }
  34.  
  35. @Override
  36. public boolean onInterceptTouchEvent(MotionEvent ev) {
  37. Log.d(TAG, "MyFrameLayout onInterceptTouchEvent " + ev.getAction());
  38.  
  39. return super.onInterceptTouchEvent(ev);
  40. }
  41. }

接着,对于 ImageView 也做类似的操作,具体代码如下:

  1. public class MyImageView extends ImageView implements OnClickListener, OnTouchListener {
  2. private static final String TAG = "Event";
  3.  
  4. public MyImageView(Context context, AttributeSet attrs) {
  5. super(context, attrs);
  6. Log.d(TAG, "MyImageView init");
  7. setOnClickListener(this);
  8. setOnTouchListener(this);
  9. }
  10.  
  11. @Override
  12. public boolean dispatchTouchEvent(MotionEvent event) {
  13. Log.d(TAG, "MyImageView dispatchTouchEvent "+ event.getAction());
  14. return super.dispatchTouchEvent(event);
  15. }
  16.  
  17. @Override
  18. public boolean onTouchEvent(MotionEvent event) {
  19. Log.d(TAG, "MyImageView onTouchEvent "+ event.getAction());
  20. return super.onTouchEvent(event);
  21. }
  22.  
  23. @Override
  24. public boolean onTouch(View arg0, MotionEvent arg1) {
  25. Log.d(TAG, "MyImageView onTouch " + arg1.getAction());
  26. return false;
  27. }
  28.  
  29. @Override
  30. public void onClick(View arg0) {
  31. Log.d(TAG, "MyImageView onClick");
  32. }
  33. }

这里要说明的是,只有ViewGroup才有 onInterceptTouchEvent 方法的,普通的 View 是没有的,它是不能对事件进行拦截的。

那这时候,如果我们点击里面的 ImageView,会有怎样的输出呢?结果如下图。

那如果点击外层呢?

0,1,2分别是代表 ACTION_DOWN,ACTION_UP,ACTION_MOVE;从中也可以看出一个点击动作包含一个Down,一个Up,还有多个Move操作。

再来看一段源码:

  1. public boolean dispatchTouchEvent(MotionEvent ev){
  2. boolean consume = false;
  3. if(onInterceptTouchEvent(ev)){
  4. consume = onTouchEvent(ev);
  5. } else{
  6. consume = child.dispatchTouchEvent(ev);
  7. }
  8. return consume;
  9. }

上述的代码把三者的关系说得很清楚了,对于一个对于一个 ViewGroup 来说,点击事件产生后,首先会传递给它,这时候会调用 dispatchTouchEvent,如果这个 ViewGroup 的 onInterceptTouchEvent 返回 true ,则表示它要拦截该事件,也就会交给它的 onTouchEvent 来进行处理。如果这个 ViewGroup 的 onInterceptTouchEvent 返回 false 则会传给子元素,子元素的 dispatchTouchEvent 就会被调用,如此反复循环。这与上面一张图打出的结果是一致的。

这里还有说明的是,如果代码设置了 OnTouchListener,那么就会先调用 onTouch 方法,然后在调用 onTouchEvent。OnClickListener 是优先级最低的,所以最后才会调用 onClick。

因此,从第二张结果图也可以看出,当存在 onTouch 之后,onTouchEvent 和 onClick 两个方法都不会在调用了。

相信到这里,大家对于View的事件分发机制有一定的了解了。

这里回到开头提的那个问题,那么有什么办法可以扩大 View 的点击区域呢?

答案:在父 View 设置 OnTouchListener 对点击事件进行拦截,通过判断点击的位置,来决定是相应子 View 的事件,还是父 View 的事件。

具体实现代码如下:

  1. public class TouchFactory {
  2.  
  3. /** 扩展垂直方向点击区域尺寸 */
  4. private static final int EXT_V_SIZE = 200;
  5.  
  6. public static View.OnTouchListener creatTouchListener(){
  7. return new View.OnTouchListener() {
  8. @Override
  9. public boolean onTouch(View v, MotionEvent event) {
  10. if (expendTouchSize(v, event)) {
  11. return true;
  12. }
  13. return false;
  14. }
  15. };
  16. }
  17.  
  18. public static boolean expendTouchSize(View root, MotionEvent event) {
  19. if (root instanceof MyFrameLayout) {
  20. ImageView view = ((MyFrameLayout) root).getMyImageView();
  21. if (view != null && view.getVisibility() == View.VISIBLE) {
  22. Rect touchRect = new Rect();
  23. view.getGlobalVisibleRect(touchRect);
  24.  
  25. int action = event.getAction();
  26. float x = event.getRawX();
  27. float y = event.getRawY();
  28.  
  29. if ((y >= touchRect.top - EXT_V_SIZE) && (y <= touchRect.bottom + EXT_V_SIZE)) {
  30. if (x >= touchRect.left) {
  31. if (action == MotionEvent.ACTION_UP) {
  32. Toast.makeText(view.getContext(), "touch", Toast.LENGTH_SHORT).show();
  33. }
  34. return true;
  35. }
  36. }
  37. }
  38. }
  39.  
  40. return false;
  41. }
  42. }

TouchFactory 对点击事件进行了封装,并通过对点击区域的判断,来决定要不要拦截点击事件。

下面是 MyFrameLayout 的具体实现。由于是一个自定义 view, 因此,变量 myImageView 是一定为空的,所以要对其进行赋值。

  1. public class MyFrameLayout extends FrameLayout {
  2.  
  3. private static final String TAG = "Event";
  4.  
  5. private MyImageView myImageView;
  6.  
  7. public MyFrameLayout(Context context) {
  8. this(context, null);
  9. }
  10.  
  11. public MyFrameLayout(Context context, AttributeSet attrs) {
  12. this(context, attrs, 0);
  13. }
  14.  
  15. public MyFrameLayout(Context context, AttributeSet attrs, int def) {
  16. super(context, attrs, def);
  17. init();
  18. }
  19.  
  20. public void init() {
  21. this.setOnTouchListener(TouchFactory.creatTouchListener());
  22. }
  23.  
  24. public MyImageView getMyImageView() {
  25. if (myImageView == null) {
  26. myImageView = findViewById(R.id.mImage);
  27. }
  28. return myImageView;
  29. }
  30. }

注意事项:当对子 View 设置 OnClickListener,点击区域刚好是子 View 内部的时候,就会消耗此事见,父 View 的拦截处理就无效了,因此,一旦选择拦截来扩大点击区域,就不要再去子 View 设置点击回调来消耗点击事件了。

Android 扩大 View 的点击区域的更多相关文章

  1. Android 自定义View——自定义点击事件

    每个人手机上都有通讯录,这是毫无疑问的,我们通讯录上有一个控件,在通讯录的最左边有一列从”#”到”Z”的字母,我们通过滑动或点击指定的字母来确定联系人的位置,进而找到联系人.我们这一节就通过开发这个控 ...

  2. android 扩大view的响应区域

    1.Android提供TouchDelegate帮助实现扩大一个很小的view的点击区域 例如:https://developer.android.com/training/gestures/view ...

  3. Android上滑手势触发和不增加布局层级扩大点击区域

    最近项目中需要实现手势上滑或者点击滑出界面的效果,实现上是利用GestureDetector,然后在onFling中判断,但遇到一个问题:手势上滑是针对整个布局的,但如果有对单独的View设置点击监听 ...

  4. Android不规则点击区域详解

    Android不规则点击区域详解 摘要 今天要和大家分享的是Android不规则点击区域,准确说是在视觉上不规则的图像点击响应区域分发. 其实这个问题比较简单,对于很多人来说根本不值得做为一篇博文写出 ...

  5. android View的点击无效的原因

    点击事件不生效,原来是因为我在里面的 ImageView中添加了 android:clickable="true". 解决办法:删掉ImageView中的android:click ...

  6. Android 自定义View可拖动移动位置及边缘拉伸放大缩小

    一.首先说一下定义这样一个View有什么用?在一些app中,需要设置头像,而用户选择的图片可能是使用摄像头拍摄,也可能是选择的相册里面的图片,总之,这样的图片大小不一,就比如在使用某个聊天软件的时候, ...

  7. Android自定义View(CustomCalendar-定制日历控件)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeas ...

  8. Android 开发 View的API 转载

    转载地址:https://blog.csdn.net/lemonrabbit1987/article/details/47704679 View类代表用户界面组件的基本构建块.一个View占据屏幕上的 ...

  9. [Android UI]View基础知识

    一.简介 在安卓中,View代表视图,是安卓中十分重要的一个概念,重要程度不亚于四大组件,用户每时每刻都在与View打交道,包括展示数据.事件传递等.因此,熟练掌握View的应用以及原理是Androi ...

随机推荐

  1. 常用Oracle进程资源查询语句(运维必看)

    (一)根据程序名称查找相关信息select A.process,B.spid,A.sid,A.serial#,A.sql_address,A.username,A.program,A.status,A ...

  2. sql语句转为Model

    在跟数据库打交道的时候,有一个常用的应用,就是把数据库中的表转为程序中的对象,也就是说表中的列转为对象的属性.对于字段比较少的,我们可以直接复制过去改,但是字段数比较多的时候,借助工具类实现比较方便而 ...

  3. AndroidStudio Frameworks detected: Android framework is detected in the project Configure

    出现这个问题应该是文件没有用正确的方式打开.   遇到这种情况,就要去检查下载的这个包的结构.       我的这个文件明显真正的是下面这个文件夹,如果把整个当做一个android文件打开会导致文件结 ...

  4. java 向上转型与向下转型

    转型是在继承的基础上而言的,继承是面向对象语言中,代码复用的一种机制,通过继承,子类可以复用父类的功能,如果父类不能满足当前子类的需求,则子类可以重写父类中的方法来加以扩展. 向上转型:子类引用的对象 ...

  5. Java集合框架(二)

    原文  http://www.jianshu.com/p/2070cb32accb List接口 查阅API,看 List 的介绍.有序的 collection (也称为序列).此接口的用户可以对列表 ...

  6. Python sort后赋值 操作陷阱

    x=[1,4,2,0] # 错误的方式,因为sort没有返回值 y=x.sort() type (y) #NoneType #正确的方式 x.sort() y=x[:]

  7. 笔记:XML-解析文档-流机制解析器(SAX、StAX)

    DOM 解析器完整的读入XML文档,然后将其转换成一个树型的数据结构,对于大多数应用,DOM 都运行很好,但是,如果文档很大,并且处理算法又非常简单,可以在运行时解析节点,而不必看到完整的树形结构,那 ...

  8. Bitmap的加载与缓存

    Android系统中图片一般用Bitmap对象表示,它支持png,jpg等常见格式.通常情况下图片的体积都比较大,单个应用允许使用的内存又是有限的,所以我们需要采取一些手段减少内存占用并提高加载速度. ...

  9. 数据库 --> SQL Server 和 Oracle 以及 MySQL 区别

    SQL Server 和 Oracle 以及 MySQL 区别 三者是目前市场占有率最高(依安装量而非收入)的关系数据库,而且很有代表性.排行第四的DB2(属IBM公司),与Oracle的定位和架构非 ...

  10. Item 15: 只要有可能,就使用constexpr

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 如果说C++11中有什么新东西能拿"最佳困惑奖" ...