天天看點

Android事件分發07——TouchDelegate的使用與解析Android事件分發07——TouchDelegate的使用與解析

  • Android事件分發07TouchDelegate的使用與解析
    • 一TouchDelegate的簡單使用
      • 1 圖示
      • 2 activity_touch_delegate_demoxml
      • 2 TouchDelegateDemoActivity
    • 二TouchDelegate源碼
    • 三主要的小小分析
      • 1 構造函數
      • 2 MotionEventACTION_DOWN
      • 3 MotionEventACTION_MOVE和MotionEventACTION_UP
      • 4 MotionEventACTION_CANCEL
      • 5 餘下的處理
    • 四流程圖

Android事件分發07——TouchDelegate的使用與解析

TouchDelegate 這個類的作用就是增大我們控件的觸摸區域。前面文章(Android事件分發05——View的onTouchEvent)中,我們在分析 onTouchEvent的時候,有個步驟是這樣的:

判斷有沒有添加TouchDelegate代理,如果添加了代理,那麼調用代理的onTouchEvent方法處理,如果這個方法傳回true,那麼就傳回true,代表消費了事件,如果沒有傳回true,那麼繼續其他處理。
public boolean onTouchEvent(MotionEvent event) {

    ...
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    ...
}
           

一、TouchDelegate的簡單使用

我們通過下面的小小例子來示範了TouchDelegate的簡單使用。

1.1 圖示

這裡面我們實作的東西很簡單,就是增大觸摸區域:如圖

Android事件分發07——TouchDelegate的使用與解析Android事件分發07——TouchDelegate的使用與解析

1.2 activity_touch_delegate_demo.xml

activity_touch_delegate_demo.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.AppCompatButton
        android:textAllCaps="false"
        android:layout_centerInParent="true"
        android:id="@+id/test_btn"
        android:text="TouchDelegate測試"
        android:layout_width="200dp"
        android:layout_height="60dp" />
</RelativeLayout>
           

1.2 TouchDelegateDemoActivity

TouchDelegateDemoActivity

public class TouchDelegateDemoActivity extends BaseActivity {
    private AppCompatButton testBtn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_touch_delegate_demo);
        testBtn = (AppCompatButton)findViewById(R.id.test_btn);
        testBtn.setOnClickListener(mOnClickListener);
        View parent = (View) testBtn.getParent();
        parent.post(new Runnable() {
            @Override
            public void run() {
                int offset = ;
                Rect rect = new Rect();
                testBtn.getHitRect(rect);
                rect.set(rect.left-offset, rect.top-offset, rect.right+offset, rect.bottom+offset);
                TouchDelegate delegate = new TouchDelegate(rect,testBtn);
                ((View)testBtn.getParent()).setTouchDelegate(delegate);
            }
        });
    }

    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(TouchDelegateDemoActivity.this, "測試而已", Toast.LENGTH_SHORT).show();
        }
    };
}
           

二、TouchDelegate源碼

public class TouchDelegate {

    /**
     * View that should receive forwarded touch events
     * 代理的view
     */
    private View mDelegateView;

    /**
     * Bounds in local coordinates of the containing view that should be mapped to the delegate
     * view. This rect is used for initial hit testing.
     * 觸摸區域
     */
    private Rect mBounds;

    /**
     * mBounds inflated to include some slop. This rect is to track whether the motion events
     * should be considered to be be within the delegate view.
     * 移動時候觸摸區域,包含了誤差
     */
    private Rect mSlopBounds;

    /**
     * True if the delegate had been targeted on a down event (intersected mBounds).
     * 标記 down 事件,是否落在了觸摸區域内
     */
    private boolean mDelegateTargeted;

    /**
     * The touchable region of the View extends above its actual extent.
     */
    public static final int ABOVE = ;

    /**
     * The touchable region of the View extends below its actual extent.
     */
    public static final int BELOW = ;

    /**
     * The touchable region of the View extends to the left of its
     * actual extent.
     */
    public static final int TO_LEFT = ;

    /**
     * The touchable region of the View extends to the right of its
     * actual extent.
     */
    public static final int TO_RIGHT = ;

    private int mSlop;

    /**
     * Constructor
     *
     * @param bounds Bounds in local coordinates of the containing view that should be mapped to
     *        the delegate view
     * @param delegateView The view that should receive motion events
     */
    public TouchDelegate(Rect bounds, View delegateView) {
        mBounds = bounds;
        //擷取相對的觸摸區域的誤內插補點(就是在這個超過原來的觸摸區域,隻要超出值小于等于這個值,都算在觸摸區域内)
        mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
        mSlopBounds = new Rect(bounds);
        mSlopBounds.inset(-mSlop, -mSlop);
        mDelegateView = delegateView;
    }

    /**
     * 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
                //沒有超出,重新設定觸摸的點,以確定我們下面調用代理的view的分發方法時,控件能夠判斷點是落在它上面的
                event.setLocation(delegateView.getWidth() / , delegateView.getHeight() / );
            } 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 * ), -(slop * ));
            }
            handled = delegateView.dispatchTouchEvent(event);
        }
        return handled;
    }
}
           

三、主要的小小分析

主要說明一下,TouchDelegate的原理,其實很簡單,就是:事件傳遞到我們的代理中來的時候,我們的判斷點有木有落在我們指定的區域内,如果落在區域内,那麼我們把點修改為控件中間的點(這樣可以確定此控件為最合适的view),如果點不在區域内,那麼我們修改點選的點(這樣可以確定此控件不是最合适的view),通過判斷要不要分發動作,如果需要那麼我們就用代理的view來分發事件。

3.1 構造函數

首先來看看 60–67行:這裡面是構造函數初始化部分

public TouchDelegate(Rect bounds, View delegateView) {
    mBounds = bounds;
    //擷取相對的觸摸區域的誤內插補點(就是在這個超過原來的觸摸區域,隻要超出值小于等于這個值,都算在觸摸區域内)
    mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
    mSlopBounds = new Rect(bounds);
    mSlopBounds.inset(-mSlop, -mSlop);
    mDelegateView = delegateView;
}
           

mBounds這個使我們按下時候判斷的區域

mSlopBounds這個是我們移動時候判斷的區域

3.2 MotionEvent.ACTION_DOWN

case MotionEvent.ACTION_DOWN:
    Rect bounds = mBounds;
    //判斷這個區域包含不包含這個點
    if (bounds.contains(x, y)) {
        //如果點在區域内,那麼标記我們有代理
        mDelegateTargeted = true;
        //标記需要發送處理到代理
        sendToDelegate = true;
    }
    break;
           

按下的時候,判斷目前的點是不是落在我們的區域内,如果是,那麼我們标記我們的初始值。

3.3 MotionEvent.ACTION_MOVE和MotionEvent.ACTION_UP

case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_MOVE:
    sendToDelegate = mDelegateTargeted;
    if (sendToDelegate) {//有代理
        Rect slopBounds = mSlopBounds;
        //判斷移動的點是否超出了範圍
        if (!slopBounds.contains(x, y)) {
            hit = false;
        }
    }
    break;
           

判斷有沒有代理,有代理,點超出了範圍,那麼設定

hit = false;

3.4 MotionEvent.ACTION_CANCEL

case MotionEvent.ACTION_CANCEL:
    //修改狀态
    sendToDelegate = mDelegateTargeted;
    mDelegateTargeted = false;
           

3.5 餘下的處理

if (sendToDelegate) {
    final View delegateView = mDelegateView;
    //判斷移動的點是否沒有超出了我們的範圍
    if (hit) {
        // Offset event coordinates to be inside the target view
        //沒有超出,重新設定觸摸的點,以確定我們下面調用代理的view的分發方法時,控件能夠判斷點是落在它上面的
        event.setLocation(delegateView.getWidth() / , delegateView.getHeight() / );
    } 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 * ), -(slop * ));
    }
    handled = delegateView.dispatchTouchEvent(event);
}
           

根據前面的動作處理以後的标記判斷,時候需要代理來處理消息,如果需要傳回false,,如果需要我們判斷,點是否已經超出了觸摸範圍,更改點的位置,調用代理view的事件分發。

四、流程圖

Android事件分發07——TouchDelegate的使用與解析Android事件分發07——TouchDelegate的使用與解析

繼續閱讀