Android 事件分發,一些被遺漏忽略的細節
1, 介面佈局 Linear Layout 包裹一個TextView。textView 上設定了按壓效果。
問:當手指從TextView 上按下,然後移動手指到TextView 邊界以外,發生那些事件?
佈局檔案如下:
<test.lee.carlyle.com.myapplication.touch.MyLinear xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="horizontal" android:id="@+id/linear" > <test.lee.carlyle.com.myapplication.touch.MYTextView android:layout_width="80dp" android:layout_height="50dp" android:background="@drawable/bg_sl" android:layout_margin="10dp" android:text="GOOD 0" /> <test.lee.carlyle.com.myapplication.touch.MYTextView android:layout_width="40dp" android:layout_height="30dp" android:background="@drawable/bg_sl" android:layout_margin="10dp" android:text="GOOD 1" /> </test.lee.carlyle.com.myapplication.touch.MyLinear>
你以為 介面上的效果是這樣???:《按壓時 TextView 變色。 拖拽過程中,當手指離開textView 後,顏色恢復到normal 狀態。》
但是你忽略掉了一點:當沒有給TextView 設定事件的時候事件流程狀態。
- 當TextView 沒有點選事件的時候,OnTouchEvent down 返回false 。 之後不再相應按壓事件。也沒有按壓效果。
- 當在TextView 上設定了點選事件後,有了按壓效果。
2,事件執行流程:
10-28 17:07:25.957 20796-20796/test.lee.carlyle.com.myapplication E/motion: MyLinear dispatch action down text View 0 action down 10-28 17:07:25.963 20796-20796/test.lee.carlyle.com.myapplication E/motion: MyLinear dispatch action move text View 0 action move 10-28 17:07:26.260 20796-20796/test.lee.carlyle.com.myapplication E/motion: MyLinear dispatch action move text View 0 action move 10-28 17:07:26.276 20796-20796/test.lee.carlyle.com.myapplication E/motion: MyLinear dispatch action move 10-28 17:07:26.277 20796-20796/test.lee.carlyle.com.myapplication E/motion: text View 0 action move 10-28 17:07:26.292 20796-20796/test.lee.carlyle.com.myapplication E/motion: MyLinear dispatch action move text View 0 action move 10-28 17:07:26.293 20796-20796/test.lee.carlyle.com.myapplication E/motion: MyLinear dispatch action up 10-28 17:07:26.294 20796-20796/test.lee.carlyle.com.myapplication E/motion: text View 0 action up
中間省略了一堆move 日誌。 可以看到在手指移出TextView 的範圍之後,仍然還將move 事件繼續分發到當前touch 焦點的試圖上。但是在手指移出textView 檢視的時候,按壓效果消失了。
之前一直以為這個drawable state change 是因為parent 發出了 state cacel 事件導致的。並以為事件將不再傳入。因為覺得合理就沒有深究。。。
那麼,既然不是 cancel 事件,那麼這個 drawable state pressed 狀態是怎麼消失的呢?
看程式碼:
原來是View 自己在move 的時候自己處理的。
3, 問題擴充套件:
- 當在父佈局中 dispatchTouchEvent 的時候,直接返回false 會怎樣? 為了驗證這個問題,我將佈局層級修改了一下,在當前佈局的外層,再增加了一層LinearLayout。
- 在dispatch TouchEvent ActionDown 的時候直接返回false: 事件將不會繼續從這層的LinearLayout 繼續分發。但是 ActionDown事件卻一次傳遞到TextView 上了。導致TextView press state change。但是無法恢復了。由於當前ViewGroup 層不分發事件,因此,事件在當前層的上層,直接交給OnTouchEvent 進行處理。
- 如果上層(TextView -> parent -> parent)的ViewGroup 的OnTouchEvent down 事件返回了ture : 後續事件將保持這個流程一直傳遞下來。
- 如果上層的ViewGroup 的OnTouchEvent down 事件返回了false:事件將繼續交給上層的上層的onTouchEvent來判斷。
可見:觸控事件流程是會在檢視樹中,的確會確定一個處理事件的View路徑的。在上層onTouch處理後,是不會再分發給下層的dispatch 去嘗試處理的。
<LinearLayout dispatchTouchDown= true; onTouchDown= true>
<LinearLayout dispatchTouchDown= false; onTouchDown= false>
<TextView/>
</LinearLayout>
</LinearLayout>
日誌如下:
10-28 17:48:40.376 22150-22150/test.lee.carlyle.com.myapplication E/motion: MyLinear dispatch-1 action down
MyLinear dispatch 2131165251 action down
text View 0 action down
10-28 17:48:40.378 22150-22150/test.lee.carlyle.com.myapplication E/motion: MyLinear onTouch -1 action down
10-28 17:48:40.388 22150-22150/test.lee.carlyle.com.myapplication E/motion: MyLinear dispatch-1 action move
MyLinear onTouch -1 action move
10-28 17:48:40.434 22150-22150/test.lee.carlyle.com.myapplication E/motion: MyLinear dispatch-1 action move
MyLinear onTouch -1 action move
10-28 17:48:40.450 22150-22150/test.lee.carlyle.com.myapplication E/motion: MyLinear dispatch-1 action move
MyLinear onTouch -1 action move
10-28 17:48:41.112 22150-22150/test.lee.carlyle.com.myapplication E/motion: MyLinear dispatch-1 action move
MyLinear onTouch -1 action move
10-28 17:48:41.128 22150-22150/test.lee.carlyle.com.myapplication E/motion: MyLinear dispatch-1 action move
10-28 17:48:41.129 22150-22150/test.lee.carlyle.com.myapplication E/motion: MyLinear onTouch -1 action move
10-28 17:48:41.130 22150-22150/test.lee.carlyle.com.myapplication E/motion: MyLinear dispatch-1 action up
MyLinear onTouch -1 action up
- 在dispatch TouchEvent 非ActionDown 的時候返回false:此時不影響分發流程。
實驗如下:
<LinearLayout dispatchTouchDown= true; onTouchDown= true>
<LinearLayout dispatchTouchDown= true; dispatchTouchMove = false; onTouchDown= false>
<TextView onCLickListenter = true />
</LinearLayout>
</LinearLayout>
日誌顯示,事件都將由TextView 進行處理。外層一致dispatch 到當前層的TextView 上。然後然後交由TextView 來處理。
疑問: 如該在TextView 中,onTouchEvent down 返回true, 其他的返回false , 又會如何呢?
實驗如下:
<LinearLayout dispatchTouchDown= true; onTouchDown= true>
<LinearLayout dispatchTouch= true; onTouchDown= false>
<TextView onCLickListenter = true ; onTouchDown= true ; onTouchMove = false />
</LinearLayout>
</LinearLayout>
實驗結果:TextView 的OnTouchEvent 在actionDown 之外返回false。並不影響事件傳遞流程。
結論:只有actionDown 事件中返回true 或 fase 會影響檢視樹的事件傳遞流程。
- 當在FrameLayout 中巢狀兩個ViewGroup. top 層 down 先返回true , 然後返回false ; bottom 層,一直返回true . 那麼事件傳遞流程會怎樣?
三、原始碼分析:
事件傳遞樹是如何構成的?在ViewGroup 中有一個屬性。這是一個鏈式結構。
private TouchTarget mFirstTouchTarget;
確定View tree 事件傳遞連結的程式碼在這裡:
這個函式在 ViewGroup的dispatchTouchEvent 中的 ActionDown 事件中執行。在此時將確定本層的TouchTarget連結串列。
其中關鍵方法:dispatchTransformedTouchEvent. 這個方法確定了是向下層分發(child.dispatch)還是向同層分發(super.dispatch:View.dispatchTouchEvent())。
可見在View.dispatchTouchEvent 中,呼叫了View.OnTouchEvent. 而View.onTouchEvent 的返回值只有在ActionDown 事件時,才能通過是否View 作為TouchTarget 新增到TouchTarget list 中的方式影響到事件分發流程。
一路向上:我們來到Activity dispatchTouchEvent 的程式碼中看看。
跟蹤程式碼發現。getWindow().superDispatchTouchEvent() 實際的處理者是 PhoneWidnow 中的DecoreView(即ViewGroup).dispatchTouchEvent()。