- 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 餘下的處理
- 四流程圖
- 一TouchDelegate的簡單使用
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 圖示
這裡面我們實作的東西很簡單,就是增大觸摸區域:如圖
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的事件分發。