博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 扩大 View 的点击区域
阅读量:5864 次
发布时间:2019-06-19

本文共 5887 字,大约阅读时间需要 19 分钟。

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

答案是有!

能够解决这个问题的前提你要对 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 设置点击回调来消耗点击事件了。

转载地址:http://huynx.baihongyu.com/

你可能感兴趣的文章
为cobbler自动化安装系统工具添加epel源
查看>>
六月第二周
查看>>
【BZOJ1179】[Apio2009]Atm (tarjan+SPFA)
查看>>
P1353 [USACO08JAN]跑步Running
查看>>
sqylog 50道练习题
查看>>
分布式ehcache缓存
查看>>
c#获取下载路径
查看>>
easyui 弹出框调用外部js函数 提示“Microsoft JScript 运行时错误: 缺少对象”
查看>>
html / css打印样式
查看>>
我的Java开发学习之旅------>Java使用ObjectOutputStream和ObjectInputStream序列号对象报java.io.EOFException异常的解决方法...
查看>>
spring boot打war包发布
查看>>
linux 平台core dump文件生成
查看>>
写在开园时
查看>>
sql处理百万级以上的数据提高查询速度的方法
查看>>
面向对象
查看>>
数据结构-字典
查看>>
Centos下MongoDB数据库的安装以及配置开机自启动(三)
查看>>
二进制基础&JAVA I/O输入输出流
查看>>
elasticsearch系列(四)部署
查看>>
Hacker(13)----搜集目标计算机的重要信息
查看>>