当我们的控件太小,导致我们无法准确的点击,这时候我们可以在在外面再加一层布局,但这样性能不太好,或者加上padding,但影响这个UI布局。这时候我们可以TouchDelegate扩大控件的点击范围。
这块其实是从之前的Android 事件分发机制(1)-源码分析_if (!canceled && !intercepted) {_沙滩捡贝壳的小孩的博客-CSDN博客
中的View的onTouchEvent中发现的
public boolean onTouchEvent(MotionEvent event) {
...
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
....
}
意思是如果设置了tochDelegate的话,那么它剩下的事件分发就要在其onTouchEvent中事件分发了
原理
然后我们去看看TouchDelegate源码
/**
* Constructor
*
* @param bounds Bounds in local coordinates of the containing view that should be mapped to
* the delegate view
* @param delegateView The view that should receive motion events
*/
public TouchDelegate(Rect bounds, View delegateView) {
mBounds = bounds;//赋值要扩大的rect区域
mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
mSlopBounds = new Rect(bounds);
mSlopBounds.inset(-mSlop, -mSlop);
mDelegateView = delegateView;//子View
}
/**
* Forward touch events to the delegate view if the event is within the bounds
* specified in the constructor.
*
* @param event The touch event to forward
* @return True if the event was consumed by the delegate, false otherwise.
*/
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)) {//如果手指落下的坐标点在扩大的rect区域内
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));
}
handled = mDelegateView.dispatchTouchEvent(event);//执行的子View的dispatchTouchEvent方法,将事件分发给子View去消费事件
}
return handled;
}
从源码中我们可以清晰的看到,其实它本质就是通过传递进去的Rect范围值,然后检测点击的坐标区域是否在Rect范围里面,如果在的话,就执行View的dispatchTouchEvent方法,将事件进行消费
应用
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_eunsure"
android:layout_width="20dp"
android:layout_height="40dp"
android:text="确认"
android:background="@color/green"
android:textSize="20sp"
android:textColor="@color/black"
android:layout_gravity="right"
android:layout_marginEnd="30dp"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"/>
final View parentView = (View) activityMainBinding.btnEunsure.getParent();
parentView.post(new Runnable() {
@Override
public void run() {
Rect rect = new Rect();
activityMainBinding.btnEunsure.getHitRect(rect);
rect.top -= 100;
rect.left -= 100;
rect.bottom += 100;
rect.right += 100;
parentView.setTouchDelegate(new TouchDelegate(rect, activityMainBinding.btnEunsure));
}
});
这样就行了