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