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

答案是有!

能够解决这个问题的前提你要对 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 的点击区域的更多相关文章

  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. 【经验随笔】MYSQL表加锁升级导致数据库访问失败

    背景:有一次定位问题发现,在同一个session连接中对MYSQL部分表加锁,导致其它未加锁的表不能访问. 用Spring管理MYSQL数据连接,在多线程访问数据库的情况下容易出问题.一个线程中对部分 ...

  2. gulp配置

    /* gulp配置 */ /* gulp配置 */ var gulp = require('gulp'), concat = require('gulp-concat'), rename = requ ...

  3. LTS和其他解决方案的比较(官方)

    主要根据LTS支持的几种任务(实时任务.定时任务.Cron任务,Repeat任务)和其他一些 开源框架在应用场景上做比较. 实时任务,实时执行 这种场景下,当任务量比较小的时候,单机都可以完成的时候. ...

  4. asp.net mvc 5 关闭xss过滤

    在控制器方法的头部添加        [ValidateInput(false)] 如果向mvc服务端提交带html标签的内容就会导致校验失败异常,从而得不到想要的结果,关闭的方法是在相应方法头部添加 ...

  5. 转: web 页面加载速度优化实战-100% 的飞跃提升

    前言 一个网站的加载速度有多重要? 反正我相信之前来 博主网站 的人至少有 50% 在加载完成前关闭了本站. 为啥捏? 看图 首页完整加载时间 8.18s,看来能进来看博主网站的人都是真爱呀,哈哈. ...

  6. LOOKUP函数入门

    2018-02-11  作者:ExcelHome  阅读:22045次       转载请注明来源"ExcelHome"并保留原文链接.固定链接:http://www.excelh ...

  7. c++趣味之难以发现的bug

    这些bug可能够你喝一壶的. 1.被断言(assert)包含的代码 常发生在切换到release版本时,执行结果乖乖的,最终查找结果是assert括号里的代码在release下是不执行的. 发现:跟踪 ...

  8. [POJ 3764] The xor-longest Path

    Description 多组数据 给你一颗树, 然后求一条最长异或路径, 异或路径长度定义为两点间简单路径上所有边权的异或和. Solution 首先 dfs 一遍,求出所有的点到根节点(随便选一个) ...

  9. 网络通信 --> select()用法

    select()用法 头文件 #include <sys/time.h> #include <sys/types.h> #include <unistd.h> 定义 ...

  10. PHP 设计模式阅读清单

    社区文章推荐 S.O.L.I.D 面向对象设计和编程(OOD&OOP)笔记 浅谈 Laravel 设计模式 PHP 完整实战 23 种设计模式 Laravel Dependency Injec ...