編輯:關於Android編程
網上很多關於Android事件分發機制的解釋,大多數描述的都不夠清晰,沒有吧來龍去脈搞清楚,本文將帶你從Touch事件產生到Touch事件被消費這一全過程作全面的剖析。
這部分牽扯到硬件和Linux內核部分;我們簡單講述一下這部分內容,如果有興趣的話可以參考這篇文章。
觸摸事件是由Linux內核的一個Input子系統來管理的(InputManager),Linux子系統會在/dev/input/
這個路徑下創建硬件輸入設備節點(這裡的硬件設備就是我們的觸摸屏了)。當手指觸動觸摸屏時,硬件設備通過設備節點像內核(其實是InputManager管理)報告事件,InputManager
經過處理將此事件傳給 Android系統的一個系統Service:WindowManagerService
。
WindowManagerService
調用dispatchPointer()從存放WindowState的z-order順序列表中找到能接收當前touch事件的 WindowState,通過IWindow代理將此消息發送到IWindow服務端(IWindow.Stub子類),這個IWindow.Stub屬於ViewRoot(這個類繼承Handler,主要用於連接PhoneWindow和WindowManagerService),所以事件就傳到了ViewRoot.dispatchPointer()中.
我們來看一下ViewRoot的dispatchPointer方法:
1 public void dispatchPointer(MotionEvent event, long eventTime,
2 boolean callWhenDone) {
3 Message msg = obtainMessage(DISPATCH_POINTER);
4 msg.obj = event;
5 msg.arg1 = callWhenDone ? 1 : 0;
6 sendMessageAtTime(msg, eventTime);
7 }
dispatchPointer方法就是把這個事件封裝成Message發送出去,在ViewRoot Handler的handleMessage中被處理,其調用了mView.dispatchTouchEvent方法(mView是一個PhoneWindow.DecorView對象),PhoneWindow.DecorView繼承FrameLayout(FrameLayout繼承ViewGroup,ViewGroup繼承自View),DecorView裡的dispatchTouchEvent方法如下. 這裡的Callback的cb其實就是Activity的attach()方法裡的設置回調。
1 //in file PhoneWindow.java
2 public boolean dispatchTouchEvent(MotionEvent ev) {
3 final Callback cb = getCallback();
4 if (mEnableFaceDetection) {
5 int pointCount = ev.getPointerCount();
6
7 switch (ev.getAction() & MotionEvent.ACTION_MASK) {
8 case MotionEvent.ACTION_POINTER_DOWN:
9
10 .......
11
12 return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
13 : super.dispatchTouchEvent(ev);
14 }
15 //in file Activity.java -> attach()
16 mFragments.attachActivity(this, mContainer, null);
17 mWindow = PolicyManager.makeNewWindow(this);
18 mWindow.setCallback(this);//設置回調
也就是說,正常情形下,當前的Activity就是這裡的cb,即調用了Activity的dispatchTouchEvent方法。
下面來分析一下從Activity到各個子View的事件傳遞和處理過程。
首先先分析Activity的dispatchTouchEvent方法。
1 public boolean dispatchTouchEvent(MotionEvent ev) {
2 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
3 onUserInteraction();
4 }
5 if (getWindow().superDispatchTouchEvent(ev)) {
6 return true;
7 }
8 return onTouchEvent(ev);
9 }
onUserInteraction() 是一個空方法,開發者可以根據自己的需求覆寫這個方法(這個方法在一個Touch事件的周期肯定會調用到的)。如果判斷成立返回True,當前事件就不在傳播下去了。 superDispatchTouchEvent(ev) 這個方法做了什麼呢? getWindow().superDispatchTouchEvent(ev) 也就是調用了PhoneWindow.superDispatchTouchEvent
方法,而這個方法返回的是 mDecor.superDispatchTouchEvent(event),在內部類 DecorView(上文中的mDecor) 的superDispatchTouchEvent 中調用super.dispatchTouchEvent(event),而DecorView繼承自ViewGroup(通過FrameLayout,FrameLayout沒有dispatchTouchEvent),最終調用的是ViewGroup的dispatchTouchEvent方法。
小結一下。Event事件是首先到了 PhoneWindow 的 DecorView 的 dispatchTouchEvent 方法,此方法通過 CallBack 調用了 Activity 的 dispatchTouchEvent 方法,在 Activity 這裡,我們可以重寫 Activity 的dispatchTouchEvent 方法阻斷 touch事件的傳播。接著在Activity裡的dispatchTouchEvent 方法裡,事件又再次傳遞到DecorView,DecorView通過調用父類(ViewGroup)的dispatchTouchEvent 將事件傳給父類處理,也就是我們下面要分析的方法,這才進入網上大部分文章講解的touch事件傳遞流程。
為什麼要從 PhoneWindow.DecorView 中 傳到 Activity,然後在傳回 PhoneWindow.DecorView 中呢? 主要是為了方便在Activity中通過控制dispatchTouchEvent 來控制當前Activity 事件的分發, 下一篇關於數據埋點文章就應用了這個機制
OK,我們要重點分析的就是ViewGroup中的dispatchTouchEvent方法。
1 @Override
2 public boolean dispatchTouchEvent(MotionEvent ev) {
3 //......
4
5 // Check for interception.
6 final boolean intercepted;//是否被攔截
7 if (actionMasked == MotionEvent.ACTION_DOWN
8 || mFirstTouchTarget != null) {//Touch按下事件
9 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
10 if (!disallowIntercept) {
11 intercepted = onInterceptTouchEvent(ev);//判斷消息是否需要被viewGroup攔截,這個方法我們可以覆寫,
12 //覆寫生效的前提是 disallowIntercept 為FALSE,否則寫了也沒用
13 ev.setAction(action); // restore action in case it was changed
14 } else {//不允許攔截
15 intercepted = false;
16 }
17 } else {
18 // There are no touch targets and this action is not an initial down
19 // so this view group continues to intercept touches.
20 //這個操作不是一開始down事件,我們把它置為TRUE,攔截之
21 intercepted = true;
22 }
23
24 // Check for cancelation.
25 final boolean canceled = resetCancelNextUpFlag(this)
26 || actionMasked == MotionEvent.ACTION_CANCEL;
27
28 //.........
29
30 final int childrenCount = mChildrenCount;//ViewGroup中子View的個數
31 if (newTouchTarget == null && childrenCount != 0) {
32 final float x = ev.getX(actionIndex);//獲取坐標,用來比對
33 final float y = ev.getY(actionIndex);
34 // Find a child that can receive the event.
35 // Scan children from front to back.
36 final View[] children = mChildren;//獲取viewgroup所有的子view
37
38 final boolean customOrder = isChildrenDrawingOrderEnabled();//子View的繪制順序
39 ////從高到低遍歷所有子View,找到能處理touch事件的child View
40 for (int i = childrenCount - 1; i >= 0; i--) {
41 final int childIndex = customOrder ?
42 getChildDrawingOrder(childrenCount, i) : i;//根據Order獲取子view
43 final View child = children[childIndex];
44 //判斷是不是我們需要的View
45 if (!canViewReceivePointerEvents(child)
46 || !isTransformedTouchPointInView(x, y, child, null)) {
47 continue;
48 }
49
50 newTouchTarget = getTouchTarget(child);//從鏈表裡找子view
51 if (newTouchTarget != null) {//找到子view
52 // Child is already receiving touch within its bounds.
53 // Give it the new pointer in addition to the ones it is handling.
54 //已經找到,循環結束,目標就是newTouchTarget
55 newTouchTarget.pointerIdBits |= idBitsToAssign;
56 break;
57 }
58
59 //.......
60 }
61 }
62 }
63 }
64
65 // Dispatch to touch targets.
66 if (mFirstTouchTarget == null) {
67 // No touch targets so treat this as an ordinary view.
68 /*dispatchTransformedTouchEvent方法中,如果child是null,那麼就調用super.dispatchTouchEvent,
69 *也就是ViewGroup的父類View的dispatchTouchEvent(如果我們在前面攔截了touch事件,那麼就會這樣處理),
70 *如果不是null,則調用child.dispatchTouchEvent。
71 **/
72 handled = dispatchTransformedTouchEvent(ev, canceled, null,
73 TouchTarget.ALL_POINTER_IDS);
74 } else {
75 //....
76 }
77 //........
78 return handled;
79 }
80 /**
81 * Transforms a motion event into the coordinate space of a particular child view,
82 * filters out irrelevant pointer ids, and overrides its action if necessary.
83 * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
84 */
85 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
86 View child, int desiredPointerIdBits) {
87 final boolean handled;
88
89 //……
90
91 // Perform any necessary transformations and dispatch.
92 if (child == null) {
93 handled = super.dispatchTouchEvent(transformedEvent);
94 } else {
95 //……
96 handled = child.dispatchTouchEvent(transformedEvent);
97 }
98
99 // Done.
100 //……
101 return handled;
102 }
我們來總結一下 ViewGroup 的 dispatchTouchEvent 的調用過程。
1 //ViewGroup.java dispatchTransformedTouchEvent方法截取
2 if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
3 event.setAction(MotionEvent.ACTION_CANCEL);
4 if (child == null) {//Event事件被截獲,調用父類View的dispatchTouchEvent方法
5 handled = super.dispatchTouchEvent(event);
6 } else {
7 handled = child.dispatchTouchEvent(event);//調用子View的dispatchTouchEvent方法
8 }
9 event.setAction(oldAction);
10 return handled;
11 }
1 public boolean dispatchTouchEvent(MotionEvent event) {
2 if (mInputEventConsistencyVerifier != null) {
3 mInputEventConsistencyVerifier.onTouchEvent(event, 0);
4 }
5
6 if (onFilterTouchEventForSecurity(event)) {
7 //noinspection SimplifiableIfStatement
8 ListenerInfo li = mListenerInfo;//View 內部類,管理一些listener
9 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
10 && li.mOnTouchListener.onTouch(this, event)) {
11 return true;
12 }
13
14 if (onTouchEvent(event)) {
15 return true;
16 }
17 }
18
19 if (mInputEventConsistencyVerifier != null) {
20 mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
21 }
22 return false;//沒有消費掉,只能返回false,讓調用者來處理了。
23 }
24 /**
25 * Register a callback to be invoked when a touch event is sent to this view.
26 * @param l the touch listener to attach to this view
27 */
28 public void setOnTouchListener(OnTouchListener l) {
29 getListenerInfo().mOnTouchListener = l;
30 }
若當前ListenerInfo 方法初始化並且 li.mOnTouchListener 的值不為空且ENABLE掩碼為Enable,那麼調用mOnTouchListener(this,event)方法。boolean onTouch(View v, MotionEvent event)
這個方法是在View的內部接口 OnTouchListener中的,是一個空方法,需要用戶自己來實現。拿一個Button來舉例;我們覆寫的onTouch()方法在這裡被調用。
1 button.setOnTouchListener(new OnTouchListener() {
2 @Override
3 public boolean onTouch(View v, MotionEvent event) {
4 //實現自己的功能
5 return true;
6 }
7 });
1 public boolean onTouchEvent(MotionEvent event) {
2 //……
3 if (((viewFlags & CLICKABLE) == CLICKABLE ||
4 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
5 switch (event.getAction()) {
6 case MotionEvent.ACTION_UP:
7 //……
8 //如果沒有觸發長按事件,手指動作是up,則執行performClick()方法
9 if (!mHasPerformedLongPress) {
10 // This is a tap, so remove the longpress check
11 removeLongPressCallback();
12
13 // Only perform take click actions if we were in the pressed state
14 if (!focusTaken) {
15 // Use a Runnable and post this rather than calling
16 // performClick directly. This lets other visual state
17 // of the view update before click actions start.
18 //這裡判斷並去執行單擊事件
19 if (mPerformClick == null) {
20 mPerformClick = new PerformClick();
21 }
22 if (!post(mPerformClick)) {
23 performClick();
24 }
25 }
26 }
27
28 break;
29 case MotionEvent.ACTION_DOWN:
30 //……
31 //是否觸發長按事件是在這裡判斷的,具體細節我就不貼出來了
32 checkForLongClick(0);
33 //……
34 break;
35 //……
36 }
37 return true;
38 }
39
40 return false;
41 }
若狀態不是CLICKABLE,那麼會直接跳過判斷執行return false,這意味著後續的touch事件不會再傳遞過來了。而大家注意看,只要是CLICKABLE,那麼無論case哪個節點,最後都是return true,這樣就保證了後續事件可以傳遞過來。 很明顯在onTouchEvent 方法裡面,主要就是判斷應該執行哪個操作,是長按還是單擊,然後去執行對應的方法。我們看看如果是單擊,執行的方法:
1 public boolean performClick() {
2 //……
3
4 ListenerInfo li = mListenerInfo;
5 if (li != null && li.mOnClickListener != null) {
6 //播放點擊音效
7 playSoundEffect(SoundEffectConstants.CLICK);
8 //執行onClick方法
9 li.mOnClickListener.onClick(this);
10 return true;
11 }
12
13 return false;
14 }
其實就是調用了我們OnClickListener裡面的onClick方法。所以說,當onTouch() 和 onClick()都存在時候,肯定是先執行onTouch,之後再執行onClick;如果onTouch 把事件截獲直接return true,那麼 onClick 方法就不會執行了。 到這裡,整個touch事件的傳遞過程我們就分析完了。
Touch事件一般調用過程總結
用戶點擊屏幕產生Touch(包括DOWN、UP、MOVE,本文分析的是DOWN)事件 -> InputManager -> WindowManagerService.dispatchPointer() -> IWindow.Stub -> ViewRoot.dispatchPointer() -> PhoneWindow.DecorView.dispatchTouchEvent() -> Activity.dispatchTouchEvent() -> PhoneWindow.superDispatchTouchEvent -> PhoneWindow.DecorView.superDispatchTouchEvent -> ViewGroup.dispatchTouchEvent() -> ViewGroup.dispatchTransformedTouchEvent() -> 子View.dispatchTouchEvent() -> 子View.onTouch() -> 子View.onTouchEvent() -> 事件被消費結束
要做這種效果1- 整個自定義控件其實就是一個ArcMenu .(半圓形那一圈),左下角的圖標沒有加入進控件中。 2- 我基於他的類改了點。他是將左下角的關閉ic
第一步、效果展示圖1、藍色的進度條圖2、紅色的進度條圖3、多條顏色不同的進度條圖4、多條顏色不同的進度條 版權聲明:本文為【歐陽鵬】原創文章,歡迎轉載,轉載請注明出處!
通知機制 是Android和用戶交互,提高APP活躍度的重要手段,可以將一些重要的信息通過通知展示給用戶,比如說新的聊天消息或者日歷事件。Notification的設計理
本文只是寫了如何配置JDK,以及adt-bundle的配置。對於以前的adt-bundle的版本,會自帶CPU/ABI系統鏡像,經過本文所描述的兩個步驟後可以直接創建AV