TouchDelegate 的作用是,运行我们增加(其实也可以减少,但没有什么意义)某个控件的触摸反馈区域。相当于我们给某个 View 添加一个委托代理,委托代理主要包含两个信息,一个是被代理的View,一个是实际期望的触摸区域,这个委托代理是由当前这个 View 的父容器来处理。当父容器获得一个touch event 的时候,会优先判断这个touch event 是否落在委托代理的区域内,如果在这个区域内,委托代理会把touch event传递给被代理的 View 。这样被代理的 View 的触摸区域就会大于它的显示区域。
一个Button在一个LinearLayout里,Button显示的区域只有那么小,但是我们又期望Button的触摸区域要稍微大些,现在如果修改Button的大小可能不符合产品UI的需求。那么我们可以为这个Button添加一个TouchDelegate。
怎么使用,具体看官方文档,https://developer.android.com/training/gestures/viewgroup.html#delegate Extend a Child View's Touchable Area 。
XML代码:
<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>
Java Code :
View parentView = findViewById(R.id.parent_layout);
parentView.post(new Runnable() {
// Post in the parent's message queue to make sure the parent
// lays out its children before you call getHitRect()
@Override
public void run() {
// The bounds for the delegate view (an ImageButton
// in this example)
Rect delegateArea = new Rect();
ImageButton myButton = (ImageButton) findViewById(R.id.button);
myButton.setEnabled(true);
myButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this,
"Touch occurred within ImageButton touch region.",
Toast.LENGTH_SHORT).show();
}
});
// The hit rectangle for the ImageButton
myButton.getHitRect(delegateArea);
// Extend the touch area of the ImageButton beyond its bounds
// on the right and bottom.
delegateArea.right += 100;
delegateArea.bottom += 100;
// 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 = new TouchDelegate(delegateArea,
myButton);
// Sets the TouchDelegate on the parent view, such that touches
// within the touch delegate bounds are routed to the child.
if (View.class.isInstance(myButton.getParent())) {
((View) myButton.getParent()).setTouchDelegate(touchDelegate);
}
}
});
在这个例子里,RelativeLayout 里有一个ImageButton,需要增加ImageButton的触摸区域。现在实例化一个 TouchDelegate 对象,实例化的时候需要一个增加后的触摸区域,以及 ImageButton。然后这个 TochDelegate 对象是设置给 Parent,也就是 RelativeLayout 。
使用的咱们不多介绍,好好看文档。接下来,咱们研究研究 TouchDelegate 怎么工作的。
咱们在 View 的 onTouchEvent() 方法里,看到了
android.view.View onTouchEvent()
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
就是说这个 View 如果有 TouchDelegate 对象的话,这个 event 会传递给 TouchDelegate 处理。那么这个 View 是谁?这个 View 不是上面例子的 ImageButton,而是 RelativeLayout,也就是 parent。同时,咱们可以看出,一个 Parent只能帮助一个 child 增加触摸区域。
接下来,咱们再看看 TouchDelegate 干了些啥。
android.view.TouchDelegate onTouchEvent()
/**
* Will 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 forwarded to the delegate, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
int x = (int)event.getX();
int y = (int)event.getY();
boolean sendToDelegate = false;
boolean hit = true;
boolean handled = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Rect bounds = mBounds;
if (bounds.contains(x, y)) {
mDelegateTargeted = true;
sendToDelegate = true;
}
break;
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) {
final View delegateView = mDelegateView;
if (hit) {
// Offset event coordinates to be inside the target view
event.setLocation(delegateView.getWidth() / 2, delegateView.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 = delegateView.dispatchTouchEvent(event);
}
return handled;
}
TouchDelegate.onTouchEvent() 在得到 MotionEvent 对象后,要做的一件事情就是判断这个 touch 的位置是不是在范围内。如果在范围内,这个事件要交给 delegateView 。delegateView 是什么?就是在构造函数里传过来的 View,也就是上面的 ImageButton。当然,如果只是直接把 MotionEvent 交给 ImageButton,touch 的位置不做改变,ImageButton 那到一个超出它区域的event,还是不行,所以,在交给 ImageButton前做了这么一件事:
event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
如果不在区域内呢。不在区域得分很多种。
首先,在ACTION_DOWN 的时候,如果不在区域内,sendToDelegate 是 false,ImageButton 不会接受到这个事件。 如果 ACTION_DOWN 的时候在区域内,ImageButton 会接收到这个事件。但是如果在 ACTION_UP 和 ACTION_MOVE 的时候超出了区域了呢。刚才 ImageButton 接收到一个 Down 事件,样式可能已经变成获得焦点的样式了,这个时候如果超出位置了,是不是需要改变样式。在上面的代码中,在 DOWN 的时候如果在区域内,sendToDelegate = true 了,然后在 MOVE 的过程中移出了区域,那么 hit = false 。那么TouchDelegate也需要告诉 ImageButton ,touch 已经不在你的范围了。
那么怎么告诉 ImageButton 呢。TouchDelegate 把 event 的位置设置成一个特殊的位置,那么ImageButton 在 dispatchTouchEvent 里就可以判定 touch move 出范围了。
event.setLocation(-(slop * 2), -(slop * 2));
这里插一句。我很不明白这里为什么会需要多定义一个 slop 变量,直接使用 mSlop 不好吗?增加一个 slop 解决了什么问题?!而且既然 x 和 y 轴都是相同的 -(slop * 2),为啥定义的 slop 不能直接等于 -(mSlop * 2)。
现在的重点是这个 slop 是毛玩意?为啥是这个值,这个值的道理是什么?
mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
mSlopBounds = new Rect(bounds);
mSlopBounds.inset(-mSlop, -mSlop);
mSlopBounds 是咱们在实例化 TouchDelegate 的时候传过来的,是要告诉 Parent ,ImageView 实际想要控制的触摸区域。在 TouchDelegate 里,它又对这个区域做了一个调整。这个 mSlop 的值大概定义在 ViewConfiguration 类。在 android.view 包下。
android.view.ViewConfiguration
private static final int TOUCH_SLOP = 8;
这个值的默认是8,实际的值会是配置文件里的一个值。也就是说,TouchDelegate 对你期望控制的触摸区域稍微缩小了范围,然后当触摸超过范围后,touch event 的位置被设置为 -(slop * 2) , - (slop * 2) 的位置。这个位置可以肯定是超过了 ImageButton 的范围,这样 ImageButton 也同样可以认为 touch event 移出了范围。
- EOF -
本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处,尊重他人劳动。
转载请注明:文章转载自 Binkery 技术博客 [https://binkery.com]
本文标题: TouchDelegate 代码分析
本文地址: https://binkery.com/archives/488.html