转载请注明出处 http://blog.csdn.net/yxhuang2008/article/details/51126009
当我们的控件太小,导致我们无法准确的点击,这个时候我们可以在这个控件外面再加一层布局,但是这样对性能不太好。其实,我们可以使用 TouchDelegate 扩大我们的点击范围。
下面是例子,我们在 LinearLayout 中放置一个 ImageButton, 然后给它们设置点击监听。
我们
看到 ImageButton 太小了,不能准确的点击
我们看看 java 代码
public class TouchDelegateActivity extends Activity {
private static final String TAG = "yxh";
private LinearLayout mLinearLayout;
private ImageButton mImageButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_touch_delegate);
mLinearLayout = (LinearLayout) findViewById(R.id.rl_touch);
mImageButton = (ImageButton) findViewById(R.id.ib_touch);
mLinearLayout.post(new Runnable() {
@Override
public void run() {
// 将 ImageButton 的 enable 设为true 确保它能接收到点击事件
mImageButton.setEnabled(true);
mImageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(TouchDelegateActivity.this, " Button touch", Toast.LENGTH_SHORT).show();
Log.i(TAG, "Button onClick ");
}
});
// 1、设置 ImageButton 可点击的范围
Rect delegateArea = new Rect();
mImageButton.getHitRect(delegateArea);
// Extend the touch area of the button beyond its bound on the right and bottom
// 2、扩大 ImageButton 的点击范围
delegateArea.right += 100;
delegateArea.bottom +=500;
// Instantiate a TouchDelegate
// 3、实例化 TouchDelegate
TouchDelegate touchDelegate = new TouchDelegate(delegateArea, mImageButton);
// Sets the TouchDelegate on the parent view, such that touches within the touch delegate
// are routed to the child.
///4、将 touchDelegate 设置到 ImageButton 的父视图上。
if (View.class.isInstance(mImageButton.getParent())){
((View)mImageButton.getParent()).setTouchDelegate(touchDelegate);
}
}
});
}
}
下面是使用 TouchDelegate 扩大控件的点击范围
1、设置 ImageButton 的点击范围
Rect delegateArea = new Rect();
mImageButton.getHitRect(delegateArea);
2、扩大 ImageButton 的点击范围,我们这里是右边增大 100 px, 底部增加了 500 px
delegateArea.right += 100;
delegateArea.bottom +=500;
3、将要扩大点击范围的控件作为参数,实例化 TouchDelegate, 我们这里传的是 ImageButton
TouchDelegate touchDelegate = new TouchDelegate(delegateArea, mImageButton);
4、将 touchDelegate 设置到 ImageButton 的父视图上
if (View.class.isInstance(mImageButton.getParent())){
((View)mImageButton.getParent()).setTouchDelegate(touchDelegate);
}
下面让我们来看看 TouchDelegate 的原理,为什么它能扩大一个控件的点击范围。
根据 View 点击事件的传递,关于点击事件的传递,可参考我的上一篇文章 【读书笔记】【Android 开发艺术探索】第3章 View 的事件体系
当一个最后会传递到它的 onTouchEvent(...) 方法,这里因为 LinearLayout 中没有可接受的点击子控件,所以
LinearLayout 会当初 View 准备自己处理点击事件。在源码 onTouchEvent(...) 方法中,有这样的几行代码。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
我们在第四步调用了 setTouchDelegate ,所以 mTouchDelegate 不为空,会执行里面的内容。 里面会调用 TouchDelegate 的 onTouchEvent(...)
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;
}
mBounds、mDelegateTargeted、mDelegateView、mSlop 这些变量是在构建 TouchDelegate 对象时创建的
public TouchDelegate(Rect bounds, View delegateView) {
mBounds = bounds;
mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
mSlopBounds = new Rect(bounds);
mSlopBounds.inset(-mSlop, -mSlop);
mDelegateView = delegateView;
}
bounds 是第 3 部传进来的点击范围,delegateView 是传进来的 ImageButton. mSlop 是系统默认为可以是活动的距离。mSlopBounds
是我们传进来的范围减去系统默认活动的距离。
在 ACTION_DOWN 中,如果我们点击的坐标在 mSlopBounds 里面,则将 mDelegateTargeted 和 sendToDelegate
设置为 true。
在 ACTION_MOVE 中,判断因为滑动的而导致点击的坐标是否还在可点击的范围之内。如果不在了,这说明不是点击的,
将 hit 设置为 false.
在最后,如果sendToDelegate 为 true ,无论 hit 是否为 true ,都重新设置点击坐标 event.setLocation(x, y). 调用
delegateView.dispatchTouchEvent(...)方法。
如果 delegate 是 View , 则会调用 View 的 dispatchTouchEvent(...) 方法。这里是 ImageButton, 所以就好执行 ImageButton
的点击事件的监听。
如果 delegate 是 ViewGroup , 则会调用 ViewGroup 的 dispatchTouchEvent(...) ,在此方法中 ViewGroup 要将点击事件分
发给它的子 View, 而在分发的过程中会调用 isTransformedTouchPointInView(...)方法判断点击的坐标是否在子 View 中。所以,
在上面才会要设置点击的坐标。
这样就是为什么 TouchDelegate 会让控件扩大了点击的范围。
更详细的内容看参考官网 http://developer.android.com/training/gestures/viewgroup.html