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

答案是有!

能够解决这个问题的前提你要对 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. JAVA通过COM接口操作PPT

    一. 背景说明 在Eclipse环境下,开发JAVA代码操作PPT,支持对PPT模板的修改.包括修改文本标签.图表.表格.满足大多数软件生成PPT报告的要求,即先收工创建好模板,在程序中修改模板数据. ...

  2. QTcreator打包发布可运行程序(基于QT5.7)

    完成C++界面程序后,我们还需要对程序进行Release,然后进行打包,才可以直接运行.我在这期间绕了一个大弯,现在记录下来我的做法供参考. 正确步骤 第一步:将构建程序改为Release,然后构建项 ...

  3. gradle build docker image

    前言:其实gradle-docker插件干的事和我们手动制作镜像是一样的,只不过它封装了一些步骤而已. eg:如果我们要将项目打包成镜像,首先我们要写Dockerfile,这是制作镜像的不可或缺的第一 ...

  4. 怎么用代码制作WordPress的归档页面

    先看看效果,这个是我网站的归档页面:http://www.shenjieblog.com/archives 其实WordPress自带了一个归档的功能,但是只能显示在网页中的某一个部分,但是我想单独制 ...

  5. 关于各种文件用Editplus的方式打开出现“向程序发送命令时出现问题”的解决方法

    其实网上有很多解决方法,但是我试过都没有用. 我出现这个错误的原因是因为我的Editplus是以管理员的方式运行的,所以解决的办法有两种: 一.修改Editplus的兼容性,方法如下: 首先在Edit ...

  6. IO查找文件复制到指定路径

    public class IOTest { // 存储获取的文件绝对路径 private static List<String> list = new ArrayList<Strin ...

  7. Facebook兆级别图片存储及每秒百万级别图片查询原理

    前言 Facebook(后面简称fb)是世界最大的社交平台,需要存储的数据时刻都在不断剧增(占比最大为图片,每天存储约20亿张,大概是微信的三倍). 那么问题来了,fb是如何存储兆级别的图片?并且又是 ...

  8. Mac环境下使用VSCode搭建Go开发环境

    换新工作啦!!!开心一下.到了新公司一看,乖乖,全MAC办公,让我这只用过windows的土包子怎么活,而且公司的人都好高冷,于是自己摸索着搭建go语言开发环境了. go语言的ide挺多的,JetBr ...

  9. Day3---------Linux操作系统目录结构

    一.Linux系统文件树状结构 "/" 根目录 "." 当前目录 .. 父目录,既上一层目录 pwd 显示当前目录路径 ls. = ls = ls/ 显示当前目 ...

  10. 小委出发vijos-1572 jdoj-1572

    小委出发 题目大意:一个长度为n的字符串,由()[]{}组成.求最长的和谐字符串:我们对最长和谐字符串的定义就是每一个括号有一个性质相同的括号与之匹配,这两个括号中间要么为空,要么也是和谐的.几个连续 ...