一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

服務器之家 - 編程語言 - JAVA教程 - 深入解析Andoird應用開發中View的事件傳遞

深入解析Andoird應用開發中View的事件傳遞

2020-04-01 14:09iam_wingjay JAVA教程

這篇文章主要介紹了深入解析Andoird應用開發中View的事件傳遞,其中重點講解了ViewGroup的事件傳遞流程,需要的朋友可以參考下

深入解析Andoird應用開發中View的事件傳遞

下面以點擊某個view之后的事件傳遞為例子。
首先分析view里的dispatchTouchEvent()方法,它是點擊view執行的第一個方法。

?
1
2
3
4
5
6
7
public boolean dispatchTouchEvent(MotionEvent event) {
 if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
   mOnTouchListener.onTouch(this, event)) {
   return true;
 }
 return onTouchEvent(event);
}

注意:里面包含兩個回調函數 onTouch(),onTouchEvent();如果控件綁定了OnTouchListener,且該控件是enabled,那么就執行onTouch()方法,如果該方法返回true,則說明該觸摸事件已經被OnTouchListener監聽器消費掉了,不會再往下分發了;但是如果返回false,則說明未被消費,繼續往下分發到該控件的onTouchEvent()去處理。

然后分析onTouchEvent()方法,進行進一步的觸摸事件處理。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (((viewFlags & CLICKABLE) == CLICKABLE ||
   (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
  switch (event.getAction()) {
   case MotionEvent.ACTION_UP:
    .....
      performClick(); //響應點擊事件
    break;
   case MotionEvent.ACTION_DOWN:
   ..... break;
   case MotionEvent.ACTION_CANCEL:
   ..... break;
   case MotionEvent.ACTION_MOVE:
   ..... break;
  }
  return true;
}
return false;

如果該控件是clickable 、long_clickable的,那么就可以響應對應事件,響應完后返回true繼續響應。比如點擊事件,先響應ACTION_DOWN,然后break并返回true,然后手抬起,又從dispatchTouchEvent()分發下來,再響應ACTION_UP,里面會去performClick()響應點擊事件。

響應點擊事件

?
1
2
3
4
5
6
7
8
9
public boolean performClick() {
  sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  if (mOnClickListener != null) {
     playSoundEffect(SoundEffectConstants.CLICK);
     mOnClickListener.onClick(this);
     return true;
  }
  return false;
}

里面執行mOnClickListener.onClick(this);即回調綁定監聽器的onClick()函數。

關鍵點:
onTouch和onTouchEvent的區別,又該如何使用?
答:
當view控件接受到觸摸事件,如果控件綁定了onTouchListener監聽器,而且該控件是enable,那么就去執行onTouch()方法,如果返回true,則已經把觸摸事件消費掉,不再向下傳遞;如果返回false,那么繼續調用onTouchEvent()事件。

Android的Touch事件傳遞到Activity頂層的DecorView(一個FrameLayout)之后,會通過ViewGroup一層層往視圖樹的上面傳遞,最終將事件傳遞給實際接收的View。下面給出一些重要的方法。

dispatchTouchEvent
事件傳遞到一個ViewGroup上面時,會調用dispatchTouchEvent。代碼有刪減

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
public boolean dispatchTouchEvent(MotionEvent ev) {
 
  boolean handled = false;
  if (onFilterTouchEventForSecurity(ev)) {
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;
 
    // Attention 1 :在按下時候清除一些狀態
    if (actionMasked == MotionEvent.ACTION_DOWN) {
      cancelAndClearTouchTargets(ev);
      //注意這個方法
      resetTouchState();
    }
 
    // Attention 2:檢查是否需要攔截
    final boolean intercepted;
    //如果剛剛按下 或者 已經有子View來處理
    if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
      final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
      if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
      } else {
        intercepted = false;
      }
    } else {
      // 不是一個動作序列的開始 同時也沒有子View來處理,直接攔截
      intercepted = true;
    }
 
     //事件沒有取消 同時沒有被當前ViewGroup攔截,去找是否有子View接盤
    if (!canceled && !intercepted) {
        //如果這是一系列動作的開始 或者有一個新的Pointer按下 我們需要去找能夠處理這個Pointer的子View
      if (actionMasked == MotionEvent.ACTION_DOWN
          || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
          || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        final int actionIndex = ev.getActionIndex(); // always 0 for down
 
        //上面說的觸碰點32的限制就是這里導致
        final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
            : TouchTarget.ALL_POINTER_IDS;
 
        final int childrenCount = mChildrenCount;
        if (newTouchTarget == null && childrenCount != 0) {
          final float x = ev.getX(actionIndex);
          final float y = ev.getY(actionIndex);
 
          //對當前ViewGroup的所有子View進行排序,在上層的放在開始
          final ArrayList<View> preorderedList = buildOrderedChildList();
          final boolean customOrder = preorderedList == null
              && isChildrenDrawingOrderEnabled();
          final View[] children = mChildren;
          for (int i = childrenCount - 1; i >= 0; i--) {
            final int childIndex = customOrder
                ? getChildDrawingOrder(childrenCount, i) : i;
            final View child = (preorderedList == null)
                ? children[childIndex] : preorderedList.get(childIndex);
 
               // canViewReceivePointerEvents visible的View都可以接受事件
               // isTransformedTouchPointInView 計算是否落在點擊區域上
            if (!canViewReceivePointerEvents(child)
                || !isTransformedTouchPointInView(x, y, child, null)) {
              ev.setTargetAccessibilityFocus(false);
              continue;
            }
 
               //能夠處理這個Pointer的View是否已經處理之前的Pointer,那么把
            newTouchTarget = getTouchTarget(child);
            if (newTouchTarget != null) {
              // Child is already receiving touch within its bounds.
              // Give it the new pointer in addition to the ones it is handling.
              newTouchTarget.pointerIdBits |= idBitsToAssign;
              break;
            }              }
            //Attention 3 : 直接發給子View
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
              // Child wants to receive touch within its bounds.
              mLastTouchDownTime = ev.getDownTime();
              if (preorderedList != null) {
                // childIndex points into presorted list, find original index
                for (int j = 0; j < childrenCount; j++) {
                  if (children[childIndex] == mChildren[j]) {
                    mLastTouchDownIndex = j;
                    break;
                  }
                }
              } else {
                mLastTouchDownIndex = childIndex;
              }
              mLastTouchDownX = ev.getX();
              mLastTouchDownY = ev.getY();
              newTouchTarget = addTouchTarget(child, idBitsToAssign);
              alreadyDispatchedToNewTouchTarget = true;
              break;
            }
 
          }
        }
 
      }
    }
 
    // 前面已經找到了接收事件的子View,如果為NULL,表示沒有子View來接手,當前ViewGroup需要來處理
    if (mFirstTouchTarget == null) {
      // ViewGroup處理
      handled = dispatchTransformedTouchEvent(ev, canceled, null,
          TouchTarget.ALL_POINTER_IDS);
    } else {
 
        if(alreadyDispatchedToNewTouchTarget) {
                   //ignore some code
          if (dispatchTransformedTouchEvent(ev, cancelChild,
              target.child, target.pointerIdBits)) {
            handled = true;
         }
        }
 
    }
  return handled;
}

上面代碼中的Attention在后面部分將會涉及,重點注意。

這里需要指出一點的是,一系列動作中的不同Pointer可以分配給不同的View去響應。ViewGroup會維護一個PointerId和處理View的列表TouchTarget,一個TouchTarget代表一個可以處理Pointer的子View,當然一個View可以處理多個Pointer,比如兩根手指都在某一個子View區域。TouchTarget內部使用一個int來存儲它能處理的PointerId,一個int32位,這也就是上層為啥最多只能允許同時最多32點觸碰。

看一下Attention 3 處的代碼,我們經常說view的dispatchTouchEvent如果返回false,那么它就不能系列動作后面的動作,這是為啥呢?因為Attention 3處如果返回false,那么它不會被記錄到TouchTarget中,ViewGroup認為你沒有能力處理這個事件。

這里可以看到,ViewGroup真正處理事件是在dispatchTransformedTouchEvent里面,跟進去看看:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
    View child, int desiredPointerIdBits) {
 
   //沒有子類處理,那么交給viewgroup處理
  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);
  }
  return handled;
}

可以看到這里不管怎么樣,都會調用View的dispatchTouchEvent,這是真正處理這一次點擊事件的地方。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dispatchTouchEvent
  public boolean dispatchTouchEvent(MotionEvent event) {
    if (onFilterTouchEventForSecurity(event)) {
    //先走View的onTouch事件,如果onTouch返回True
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null
        && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event)) {
      result = true;
    }
 
    if (!result && onTouchEvent(event)) {
      result = true;
    }
  }
    return result;
  }

我們給View設置的onTouch事件處在一個較高的優先級,如果onTouch執行返回true,那么就不會去走view的onTouchEvent,而我們一些點擊事件都是在onTouchEvent中處理的,這也是為什么onTouch中返回true,view的點擊相關事件不會被處理。

小小總結一下這個流程
ViewGroup在接受到上級傳下來的事件時,如果是一系列Touch事件的開始(ACTION_DOWN),ViewGroup會先看看自己需不需要攔截這個事件(onInterceptTouchEvent,ViewGroup的默認實現直接返回false表示不攔截),接著ViewGroup遍歷自己所有的View。找到當前點擊的那個View,馬上調用目標View的dispatchTouchEvent。如果目標View的dispatchTouchEvent返回false,那么認為目標View只是在那個位置而已,它并不想接受這個事件,只想安安靜靜的做一個View(我靜靜地看著你們裝*)。此時,ViewGroup還會去走一下自己dispatchTouchEvent,Done!

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 小早川怜子亚洲综合中文字幕 | 99久热只有精品视频免费看 | 美女脱一光二净的视频 | 色吧| 成人精品在线 | 免费观看欧美成人h | 日本成熟| 999任你躁在线精品免费不卡 | 亚洲视频久久 | 毛片段| 我的妹妹最近有点怪在线观看 | 免费成年人在线视频 | 美女一级ba大片免色 | 视频在线网站 | 视频大全在线观看免费 | 欧美特黄视频在线观看 | www.九九热| 国产一页| 国语对白做受xxxx | 80日本xxxxxxxxx96 7个黑人玩北条麻妃 | 国产欧美精品一区二区三区四区 | 91无套极品外围在线播放 | 赤坂丽女医bd无删减在线观看 | 日本小视频免费 | 亚洲 日韩 自拍 视频一区 | 热99re久久精品精品免费 | 91制片厂制作果冻传媒2021 | 暴露狂婷婷 | 天天综合五月天 | 国产亚洲精品看片在线观看 | 女人扒开下面让男人桶爽视频 | 教师系列 大桥未久在线 | 国内免费高清视频在线观看 | 国产成人手机在线 | 亚洲色域网 | 日韩美女强理论片 | 亚洲网站在线 | 精品国产在天天线在线麻豆 | 日韩拍拍拍 | 韩国www| 国产精品日韩在线观看 |