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

答案是有!

能够解决这个问题的前提你要对 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. 笔记-JS高级程序设计-基本概念篇

    1:JS中的一切(变量,函数名和操作符)都是区分大小写的 2:标识符(变量,函数,属性的名字,以及函数的参数),第一个字符必须是字母,下划线,或者美元$,书写方式采用驼峰式,不能将关键字作为标识符. ...

  2. 纯css3打造瀑布流布局

    纯css3打造瀑布流布局 原理: 1.column-count 把div中的文本分为多少列 2.column-width 规定列宽 3.column-gap 规定列间隙 4.break-inside: ...

  3. Centos虚拟机克隆模板

    Centos6模板 IPTABLES/SELINUX # iptalbes -F # service iptables save 或 # /etc/init.d/iptables stop # chk ...

  4. Python进阶_面对对象&面对过程

    这节主要讲面对对象与面对过程两种编程思想的主要区别. 一. 简单对比 面向过程是一种基础的方法,它考虑的是实际的实现步骤,一般情况下,面向过程是自顶向下逐步求精,其最重要的是模块化的思想方法. 面向对 ...

  5. javascript中词法环境、领域、执行上下文以及作业详解

    词法环境(Lexical Environments) 官方规范对词法环境的说明是:词法环境(Lexical Environments)是一种规范类型,用于根据ECMAScript代码的词法嵌套结构来定 ...

  6. Android短信验证码倒计时

    有两种实现方法 1.第一种方式:Timer /** * Description:自定义Timer * <p> * Created by Mjj on 2016/12/4. */ publi ...

  7. 【CSS】 CSS的一些应用实例和参考

    css 一些应用实例 基本抄自http://www.w3school.com.cn/css/css_align.asp ..把这些知识消化吸收然后以自己的话来解释一下 ■ 对齐 ●  用margin属 ...

  8. linux --> Event Loop介绍

    Event Loop介绍 想要理解Event Loop,就要从程序的运行模式讲起.运行以后的程序叫做"进程"(process),一般情况下,一个进程一次只能执行一个任务. 如果有很 ...

  9. 路径字符串数据转化为树型层级对象,path to json tree

    由于项目中使用了react 及 ant-design ,在使用tree树型控件时,需要 类似下面的数据, const treeData = [{ title: '0-0', key: '0-0', c ...

  10. 想不到的:js中加号操作符

    研究js加号操作符的时候,无意中试验了一个 console.log({} + "str");//NaN 发现结果居然是NaN,这让我百思不得其解. 我查阅资料,js高级编程里是这样 ...