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

答案是有!

能够解决这个问题的前提你要对 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. html onclick时间传字符串参数

    经常忘记拼html中onclick事件传字符串引号的问题,所以今天记一下! 简单一句话 外面是"",里面就是'',外边是'',里边就是"".   示例: var ...

  2. webapi下的web请求

    先看webapi提供的服务: [HttpPost] public ResultBaseModel SiteList(SiteModel param) { ResultBaseModel resultM ...

  3. Java仪器数据文件解析-PDF文件

    一.概述 使用pdfbox可生成Pdf文件,同样可以解析PDF文本内容. pdfbox链接:https://pdfbox.apache.org/ 二.PDF文本内容解析 File file = new ...

  4. EF+Redis(StackExchange.Redis)实现分布式锁,自测可行

    电商平台 都会有抢购的情况,比如 1元抢购. 而抢购 最重要的 就是库存,很多情况下  库存处理不好,就会出现超卖现象. 本文将用redis为缓存,StackExchange 框架,消息队列方式 实现 ...

  5. char码值对应列表大全

    Char("0") 为0的字符Char("1") Char("2") Char("3") Char("4&qu ...

  6. 关于一道面试题【字符串 '1 + (5 - 2) * 3',怎么算出结果为10,'eval'除外】

    最近徘徊在找工作和继续留任的纠结之中,在朋友的怂恿下去参加了一次面试,最后一道题目是: 写一个函数,输入一个字符串的运算式,返回计算之后的结果.例如这样的: '1 + (5 - 2) * 3',计算出 ...

  7. Sharepoint对List增删改操作

    在sharepoint的开发过程中,用得最多的,就是获取sharepoint的列表数据,以及对其进行一系列的增删改查等操作,这里主要是记录一些常用的操作,通过对象模型方式对数据进行增删改查. 由于sh ...

  8. 设计模式 --> (5)适配器模式

    适配器模式 适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法在一起工作的两个类能够在一起工作.比如说我的hp笔记本,美国产品,人家美国的电压是110V的,而我们中国的 ...

  9. Algorithm --> 求阶乘末尾0的个数

    求阶乘末尾0的个数 (1)给定一个整数N,那么N的阶乘N!末尾有多少个0?比如:N=10,N!=3628800,N!的末尾有2个0. (2)求N!的二进制表示中最低位为1的位置. 第一题 考虑哪些数相 ...

  10. Hibernate学习(1)- 初识

    一.概念引入 1.持久化: 狭义概念:数据存储在物理介质不会丢失. 广义概念:对数据的CRUD操作都叫做持久化. 2.加载: hibernate里,数据从数据库中加载到session. 3.ORM - ...