Android 小技巧之扩大点击区域

这里介绍 Android 开发中的一个小技巧:扩大 View 的 touchable area。扩大 touchable area 后可以让 View 的可点击区域大于其自身显示区域,某些场景下很有用。

TouchDelegate

以下是 TouchDelegate 的文档。

Helper class to handle situations where you want a view to have a larger touch area than its actual view bounds. The view whose touch area is changed is called the delegate view. This class should be used by an ancestor of the delegate. To use a TouchDelegate, first create an instance that specifies the bounds that should be mapped to the delegate and the delegate view itself.

The ancestor should then forward all of its touch events received in its View.onTouchEvent(MotionEvent) to onTouchEvent(android.view.MotionEvent).

看文档可知 TouchDelegate 就是专门为修改 view 的点击区域而生。

以下翻译至官方文档

Android 中的 TouchDelegate 类可用于扩展子节点的 touchable area,让子节点的 touchable area 可以超出其本身的边界。当子节点很小,需要更大 touch 区域时这个技巧很有用。当然,你也可以使用这个技巧来缩小 touch 区域。

以下例子中,ImageButton 是代理 view (即,父节点会扩展其 touch 区域)。布局文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/parent_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >

<ImageButton android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:src="@drawable/icon" />
</RelativeLayout>

以下的代码片断操作这几个操作:

  • 获取父节点,并且在主线程中发一个 Runnable 消息。这可以保证调用 getHitRect() 方法前父节点已完成子节点的布局工作。getHitRect() 方法用于获取子节点的点击区域(touchable area)
  • 获取子节点(在这里是 ImageButton),并且调用 getHitRect() 来获取其 touchable area 的边界
  • 扩展 ImageButton 可点击区域的边界
  • 实例化一个 TouchDelegate,传入的构造参数包括点击区域边界以及 ImageButton 对象本身
  • 在父节点上设置 TouchDelegate,这样能让在 touch delegate 边界内发生的 touch event 被路由到 ImageButton

作为 ImageButton 这个子节点的 touch delegate,父节点将收到所有的 touch event。如果 touch event 发生在子节点的点击区域内,父节点会将这些 touch event 传递给子节点处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

// Post in the parent's message queue to make sure the parent
// lays out its children before you call getHitRect()
findViewById<View>(R.id.parent_layout).post {
// The bounds for the delegate view (an ImageButton
// in this example)
val delegateArea = Rect()
val myButton = findViewById<ImageButton>(R.id.button).apply {
isEnabled = true
setOnClickListener {
Toast.makeText(
this@MainActivity,
"Touch occurred within ImageButton touch region.",
Toast.LENGTH_SHORT
).show()
}

// The hit rectangle for the ImageButton
getHitRect(delegateArea)
}

// Extend the touch area of the ImageButton beyond its bounds
// on the right and bottom.
delegateArea.right += 100
delegateArea.bottom += 100

// Sets the TouchDelegate on the parent view, such that touches
// within the touch delegate bounds are routed to the child.
(myButton.parent as? View)?.apply {
// Instantiate a TouchDelegate.
// "delegateArea" is the bounds in local coordinates of
// the containing view to be mapped to the delegate view.
// "myButton" is the child view that should receive motion
// events.
touchDelegate = TouchDelegate(delegateArea, myButton)
}
}
}
}

Demo

我自己动手将这个示例改了一下,代码见 Github

截图如下:

从截图可以看出,扩大 ImageButton 的 touchable area 后,其点击区域也随着变大。

参考

官方文档