在 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点击区域的资料请关注代码网其它相关文章!
发表评论