当前位置: 代码网 > it编程>App开发>Android > Android实现扩大View点击区域的三种方式

Android实现扩大View点击区域的三种方式

2024年09月07日 Android 我要评论
在 android 应用开发中,有时候需要扩大 view 的点击区域以提高用户交互的便利性,尤其是当视图元素较小或用户界面密集时。扩大点击区域可以让用户更容易点击目标,改善用户体验。以下提供几种扩大点

在 android 应用开发中,有时候需要扩大 view 的点击区域以提高用户交互的便利性,尤其是当视图元素较小或用户界面密集时。扩大点击区域可以让用户更容易点击目标,改善用户体验。以下提供几种扩大点击区域的思路。

方式一:增加padding

通过设置padding来增大点击区域,如:

<textview
        android:id="@+id/tv_view_delegate2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/gray_holo_light"
        android:padding="20dp"
        android:text="touchdelegate2" />

上面的代码通过在xml中设置padding来扩大点击区域,当然也可以通过代码设置setpadding来实现。虽然设置padding可以起到效果,但是如果使用不当可能会影响视图的布局和外观,比如对imageview设置padding的话可能会挤压其形状,所以使用padding扩大点击区域时需要确保不影响视图的布局和外观。

方式二:touchdelegate

touchdelegate 类是 android 中的一个辅助类,用于扩展 view 的触摸区域,使其大于实际的 view 边界。这对于增加某些 ui 元素的触控便捷性非常有用,比如小按钮。

touchdelegate 使用示例:

/**
 * 扩展方法,扩大点击区域
 * note: 需要保证目标targetview有父view,否则无法扩大点击区域
 *
 * @param expandsize 扩大的大小,单位px
 */
fun view.expandtouchview(expandsize: int = 10.dp2px()) {
    val parentview = (parent as? view)
    parentview?.post {
        val rect = rect()
        gethitrect(rect) //gethitrect(rect)将视图在父容器中所占据的区域存储到rect中。
        log("rect = $rect")
        rect.left -= expandsize
        rect.top -= expandsize
        rect.right += expandsize
        rect.bottom += expandsize
        log("expandrect = $rect")
        parentview.touchdelegate = touchdelegate(rect, this)
    }
}

在activity中使用:

private val tvexpandtouch: textview by id(r.id.tv_view_delegate)

tvexpandtouch.run {
    expandtouchview(50.dp2px()) //扩大点击区域
    setonclicklistener { showtoast("通过touchdelegate扩大点击区域") }
}

上面就实现了view扩大点击区域,继续来看下touchdelegate 的源码:

public class touchdelegate {
    private view mdelegateview; //需要接收触摸事件的 view,即代理 view。
    private rect mbounds;//本地坐标中的代理 view 的边界,用于初始命中测试。
    private rect mslopbounds;//增加一定范围的 mbounds,用于追踪触摸事件是否应被视为在代理 view 内。
    @unsupportedappusage
    private boolean mdelegatetargeted;
    private touchdelegateinfo mtouchdelegateinfo;

    public touchdelegate(rect bounds, view delegateview) {
        mbounds = bounds;
        mslop = viewconfiguration.get(delegateview.getcontext()).getscaledtouchslop();
        mslopbounds = new rect(bounds);
        mslopbounds.inset(-mslop, -mslop);
        mdelegateview = delegateview;
    }

    //接收并处理触摸事件。若事件在 mbounds 内,则会将其转发到 mdelegateview。
    public boolean ontouchevent(@nonnull motionevent event) {
        int x = (int)event.getx();
        int y = (int)event.gety();
        boolean sendtodelegate = false;
        boolean hit = true;
        boolean handled = false;

        switch (event.getactionmasked()) {
            case motionevent.action_down:
                mdelegatetargeted = mbounds.contains(x, y);
                sendtodelegate = mdelegatetargeted;
                break;
            case motionevent.action_pointer_down:
            case motionevent.action_pointer_up:
            case motionevent.action_up:
            case motionevent.action_move:
                sendtodelegate = mdelegatetargeted;
                if (sendtodelegate) {
                    rect slopbounds = mslopbounds;
                    if (!slopbounds.contains(x, y)) {
                        hit = false;
                    }
                }
                break;
            case motionevent.action_cancel:
                sendtodelegate = mdelegatetargeted;
                mdelegatetargeted = false;
                break;
        }
        if (sendtodelegate) {
            if (hit) {
                // offset event coordinates to be inside the target view
                event.setlocation(mdelegateview.getwidth() / 2, mdelegateview.getheight() / 2);
            } else {
                // offset event coordinates to be outside the target view (in case it does
                // something like tracking pressed state)
                int slop = mslop;
                event.setlocation(-(slop * 2), -(slop * 2));
            }
            //note:重点看这里,最终是调用的代理view去处理事件了。
            handled = mdelegateview.dispatchtouchevent(event);
        }
        return handled;
    }
}

view.java 源码中使用 touchdelegate

   private touchdelegate mtouchdelegate = null;
    
   public void settouchdelegate(touchdelegate delegate) {
       mtouchdelegate = delegate;
   }

   public touchdelegate gettouchdelegate() {
       return mtouchdelegate;
   }

   public boolean ontouchevent(motionevent event) {
      //......
      if (mtouchdelegate != null) {
          if (mtouchdelegate.ontouchevent(event)) {
              return true;
          }
      }
      switch (action) {
          case motionevent.action_down:
          //...省略...
          case motionevent.action_cancel:
          case motionevent.action_up:
      }
    }     

可以看到在ontouchevent中,优先去判断是否有touchdelegate,如果有的话会先去找对应的代理view去处理事件。使用touchdelegate的注意事项:

  • 目标view必须有父view
  • 给多个目标view扩大点击区域时,不能是同一个父view,从view类的源码中可以看到,设置settouchdelegate时,会把之前的覆盖掉

方式三:rectf & getlocationonscreen

rectf 是一个用于表示浮点坐标的矩形区域的类,而 getlocationonscreen 则用于获取视图在整个屏幕中的绝对坐标。结合两者,可以检查触摸事件是否在子视图的“扩展区域”内,然后执行相应的操作。代码示例:

class parentinnertouchview @jvmoverloads constructor(
    context: context,
    attrs: attributeset? = null,
    defstyleattr: int = 0
) : constraintlayout(context, attrs, defstyleattr) {

    private val tvchildview: textview

    init {
        inflate(context, r.layout.expand_touch_view, this)
        tvchildview = findviewbyid(r.id.tv_expand_view)
        tvchildview.setonclicklistener { showtoast("扩大了点击事件") }
    }

    @suppresslint("clickableviewaccessibility")
    override fun ontouchevent(event: motionevent?): boolean {
        event?.let { ev ->
            if (ev.action == motionevent.action_down) {
                val ischildhit = ishitexpandchildview(tvchildview, pair(ev.rawx, ev.rawy))
                if (ischildhit) {
                    //将事件传递给子控件
                    tvchildview.performclick()
                }
            }
        }
        return super.ontouchevent(event)
    }

    /**
     * 判断是否点击到了子 view 的扩大区域
     * @param childview 子 view
     * @param touchpair 点击的位置 (x, y)
     * @param expandsize 扩大区域的大小
     * @return 是否命中
     */
    private fun ishitexpandchildview(
        childview: view,
        touchpair: pair<float, float>,
        expandsize: int = 50.dp2px()
    ): boolean {

        // 获取子 view 在屏幕上的位置
        val location = intarray(2)
        childview.getlocationonscreen(location)

        val childx = location[0].tofloat()
        val childy = location[1].tofloat()
        val touchx = touchpair.first
        val touchy = touchpair.second

        // 扩大点击区域
        val rect = rectf()
        rect.set(
            childx - expandsize,
            childy - expandsize,
            childx + childview.width + expandsize,
            childy + childview.height + expandsize
        )
        // 判断点击是否在扩大的子 view 区域内
        return rect.contains(touchx, touchy)
    }
}
  • getlocationonscreen: 用于获取子视图在屏幕上的绝对坐标,返回一个包含 x 和 y 坐标的数组。利用这些坐标计算出子视图在屏幕上的位置。
  • rectf: 创建一个矩形区域,通过调用 set 方法扩展矩形的上下左右边界,从而扩大点击区域。
  • ontouchevent: 监听触摸事件,如果点击位置在扩大的区域内,则调用 performclick 触发子视图的点击事件。

以上就是android实现扩大view点击区域的三种方式的详细内容,更多关于android view点击区域的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com