转账请注明出处 http://blog.csdn.net/yxhuang2008/article/details/51126009
当我们的控件太小,导致我们无法准确的点击,这个时候我们可以在这个控件外面再加一层布局,但是这样对性能不太好。其实,我们可以使用 TouchDelegate 扩大我们的点击范围。
下面是例子,我们在 LinearLayout 中放置一个 ImageButton, 然后给它们设置点击监听。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rl_touch" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageButton android:id="@+id/ib_touch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_marginTop="100dp" android:layout_gravity="center_horizontal" android:background="@android:color/darker_gray" android:src="@mipmap/clean_normal"/> </LinearLayout>我们 看到 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
<span style="white-space:pre"> </span>delegateArea.right += 100; <span style="white-space:pre"> </span>delegateArea.bottom +=500;3、将要扩大点击范围的控件作为参数,实例化 TouchDelegate, 我们这里传的是 ImageButton
<span style="white-space:pre"> </span>TouchDelegate touchDelegate = new TouchDelegate(delegateArea, mImageButton);4、将 touchDelegate 设置到 ImageButton 的父视图上
<span style="white-space:pre"> </span>if (View.class.isInstance(mImageButton.getParent())){ ((View)mImageButton.getParent()).setTouchDelegate(touchDelegate); }
下面让我们来看看 TouchDelegate 的原理,为什么它能扩大一个控件的点击范围。
根据 View 点击事件的传递,关于点击事件的传递,可参考我的上一篇文章 【读书笔记】【Android 开发艺术探索】第3章 View 的事件体系
当一个最后会传递到它的 onTouchEvent(...) 方法,这里因为 LinearLayout 中没有可接受的点击子控件,所以
LinearLayout 会当初 View 准备自己处理点击事件。在源码 onTouchEvent(...) 方法中,有这样的几行代码。
<span style="white-space:pre"> </span>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