Android 扩大 View 的点击区域的方法

 更新时间:2018年04月08日 09:05:58   作者:huansky  
这篇文章主要介绍了Android 扩大 View 的点击区域的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

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

答案是有!

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • 基于Alarmmanager实现简单闹钟功能

    基于Alarmmanager实现简单闹钟功能

    这篇文章主要为大家详细介绍了基于Alarmmanager实现简单闹钟功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-06-06
  • 一文彻底了解Android中的线程和线程池

    一文彻底了解Android中的线程和线程池

    众所周知线程池能很高地提升程序的性能,下面这篇文章主要给大家介绍了关于Android中线程和线程池的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • android中px和dp,px和sp之间的转换方法

    android中px和dp,px和sp之间的转换方法

    在Android开发中dp和px,sp和px之间的转换时必不可少的。下面脚本之家小编给大家带来了android中px和dp,px和sp之间的转换方法,感兴趣的朋友一起看看吧
    2018-06-06
  • 另外两种Android沉浸式状态栏实现思路

    另外两种Android沉浸式状态栏实现思路

    这篇文章主要为大家介绍了另外两种Android沉浸式状态栏实现思路,android5.0及以后版本都支持给状态栏着色,而目前android主流版本还是4.4,想要深入了解的朋友可以参考一下
    2016-01-01
  • Android Room数据库自动升级与迁移的策略

    Android Room数据库自动升级与迁移的策略

    在 Android 应用开发中,Room 是 Google 提供的一个轻量级数据库框架,用于简化与 SQLite 的交互,本文将介绍 Room 数据库升级的几种场景和常见的处理方法,包括手动迁移和自动迁移的策略,需要的朋友可以参考下
    2024-09-09
  • Android嵌套滑动冲突的解决方法

    Android嵌套滑动冲突的解决方法

    本篇文章主要介绍了Android嵌套滑动冲突的解决方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • Android-SPI学习笔记

    Android-SPI学习笔记

    这篇文章主要介绍了Android-SPI的相关资料,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下
    2021-02-02
  • Android那两个你碰不到但是很重要的类之ActivityThread

    Android那两个你碰不到但是很重要的类之ActivityThread

    上篇文章我们聊了些Android里那些我们平时碰不到但很重要的类ViewRootImpl,这一篇我们就来看看另外那个类ActivityThread,文中有相关的代码示例,感兴趣的同学可以跟着小编一起来学习
    2023-05-05
  • android自定义View圆圈拖动

    android自定义View圆圈拖动

    这篇文章主要为大家详细介绍了android自定义View圆圈拖动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-04-04
  • android 获取屏幕像素大小的正确方法

    android 获取屏幕像素大小的正确方法

    android 获取屏幕像素大小的正确方法,需要的朋友可以参考一下
    2013-05-05

最新评论