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

来源:互联网 发布:java考勤管理系统源码 编辑:程序博客网 时间:2024/04/28 19:05

转载请注明出处 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">    <ImageButtonandroid: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










0 0
原创粉丝点击