使用 TouchDelegate 扩大控件的点击范围

转账请注明出处 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 的整个过程。


下面让我们来看看 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










你可能感兴趣的:(android,android的点击事件)