*哈哈,第一次使用markdown,看著挺高大上的啊。如果順手了,會直接切換默認為markdown。*
話說關于android事件分發的博客真的不在少數,基本都是基于源碼分析+實例代碼的形式講解。今天的這篇博客呢,主要的側重點并不是在事件分發上,而是在事件的轉換上。
為什么需要事件轉換? 打個比方吧:
> 我們點擊一個TextView的左上角,加入這個TextView在它老子的中間位置,那我們點擊的x/y應該是多少呢? 在它老子那這兩個值可能是100/100,而在TextView上打印就會是1/1了,也就是說在事件分發給兒子之前會有一次事件的剪裁過程,這個過程稍后我們也會在源碼中找到。
大家都知道事件的分發都是從 `dispatchTouchEvent()` 方法開始的,但是我們一般很少去重寫 `dispatchTouchEvent` 方法,原因就是盡量避免破壞android原生的事件分發機制。但是今天我們就來試這重寫一下 `dispatchTouchEvent` 方法,從最簡單的代碼探究事件的剪裁。(做好心理準備, 就兩行代碼)。
~~~
public class MyViewGroup extends LinearLayout {
private View mFirstView;
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mFirstView = getChildAt(0);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
float x = ev.getX();
float y = ev.getY();
System.out.println("老子:x = " + x + ", y = " + y);
if(x < mFirstView.getLeft() || x > mFirstView.getRight()) return true;
if(y < mFirstView.getTop() || y > mFirstView.getBottom()) return true;
int offetX = getScrollX() - mFirstView.getLeft();
int offetY = getScrollY() - mFirstView.getTop();
ev.offsetLocation(offetX, offetY);
handled = mFirstView.dispatchTouchEvent(ev);
ev.offsetLocation(-offetX, -offetY);
return handled;
}
}
~~~
代碼很簡單,我們只需要關注`dispatchTouchEvent` 方法就ok, 可以看到 在`dispatchTouchEvent` 方法中,我們并沒有任何 `super.dispatchTouchEvent` 的調用地方,也就是說,我們完全重寫了android默認的事件分發機制。
現在我們來分析一下這段的代碼。
前面的略過,21行代碼打印了我們觸摸的坐標。
23~24行,我們先不去管它。先去看下面的代碼。
26~28行代碼:
~~~
int offetX = getScrollX() - mFirstView.getLeft();
int offetY = getScrollY() - mFirstView.getTop();
ev.offsetLocation(offetX, offetY);
~~~
首先我們計算了offsetX和offsetY值,這兩個值為什么要這么計算得出呢,我是怎么知道要這么計算的呢? 這里的答案是:看的android源碼的事件剪裁~~~ 我是直接copy出android的源碼來放這的,然后咱們再去理解他,要去理解它,我們還需要一張生動形象高端大氣的圖才ok。

來看圖, scrollX代表這綠色部分在屏幕外面的部分,假如這里是50,
left是藍色部分距離他老子(綠色部分)左邊的值,即`getLeft`這里是100,
當我們觸摸屏幕的時候,坐標是從屏幕的左上角開始計算,從這張圖來看,我們觸摸的位置在綠色部分其實是50(100 - 10)的位置.
ok,在來看看`offsetX` 如果還是拿這張圖來說的話, `offsetX = 50 - 100 = -50` 。offsetY的值也相同。
說到這里,我們大概明白`MotionEvent.offsetLocation` 的作用了,它的作用就是根據你的兩個參數去偏移坐標。這里,我們的x偏移了-50,計算一下,如果將這個剪裁后的事件分發給子view,那對于子view而言,點擊的位置就是0了。哈哈,終于走通了。
那接下來,我們就通過log來驗證咱們的猜想吧。


看log驗證了我們的猜想,事件的坐標的確是經過了剪裁。
而,我們代碼中那兩個判斷:
~~~
if(x < mFirstView.getLeft() || x > mFirstView.getRight()) return true;
if(y < mFirstView.getTop() || y > mFirstView.getBottom()) return true;
~~~
主要作用就是防止該事件沒有發生到該view身上,而強制分發出去了。
最后,我們再去看看源碼中是怎么操作的,是不是和我們的邏輯一樣。
~~~
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
...
...
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
~~~
看18~22行,代碼和我們的一樣!!!(其實是我們抄的源碼,哈哈)。完結。
哦,對了,本篇博客并沒有什么實質性的意義,就是去探究一下android事件在分發過程的剪裁,樹立一個思想:
> 對于view而言,事件發生到我身上,我就把它看作我自己的,你點我拿,我就給你報哪。
在實際代碼中,我們還是盡可能的避免重寫`dispatchTouchEvent`方法,畢竟這里是android默認的事件分發機制。
真正完畢了,吃飯去鳥~~~