Native原生相比于Hybrid或H5最大优点是具有流畅和复杂的交互效果,触摸事件便是其中重要一项,包括点击(Click)、长按(LongClick)、手势(gesture)等。
以最简单常见的点击(Click)为例,Native组件可以自定义selector,使得被点击的组件具有动态效果,Android 5.0以上甚至可以有涟漪效果(Material Design)。而这些在Hybrid或H5中很难实现,很多时候区分它们与原生最简单的方法就是检验点击交互效果。
React-Native的强大之处在于实现了较为全面的Touch事件机制,虽然仍略有缺陷,但相比于Hybrid或H5的体验而言,已经足足提高了一大截,下面分析讲解一下其实现原理,和具体使用方式。
1、Touch事件机制
如果阅读过React-Native源码的话,应该了解React-Native页面的UI根视图是ReactRootView,包路径是:com.facebook.react.ReactRootView,它是FramLayout的一个子类。
首先,来看一下ReactActivity这个页面基类,ReactRootView是如何作为React-Native的根视图被初始化及添加的。
在ReactActivity的onCreate这个生命周期里,直接实列化,然后作为当前Window的ContentView,也就可以认为其是所有React-Native组件的根视图。
熟悉Android触摸事件机制的,应该知道视图树中,触摸事件是逐级传递的,每个视图(View)中有两个接收和处理Touch事件的方法,分别是onInterceptTouchEvent和onTouchEvent,这两个方法的区别为:
onInterceptTouchEvent的传递层级是由父视图向子视图,顾名思义,通常用作事件拦截。
onTouchEvent的传递层级是由子视图向父视图,通常用作事件处理。
我们来看一下ReactRootView的事件接收和处理。
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">public</span> <span class="hljs-class" style="Box-sizing: border-Box;"><span class="hljs-keyword" style="color: rgb(0,102);">ReactRootView</span> <span class="hljs-keyword" style="color: rgb(0,102);">SizeMonitoringFrameLayout</span> <span class="hljs-keyword" style="color: rgb(0,102);">RootView</span> {</span> ... <span class="hljs-annotation" style="color: rgb(155,136); Box-sizing: border-Box;">boolean</span> <span class="hljs-title" style="Box-sizing: border-Box;">onInterceptTouchEvent</span>(MotionEvent ev) { handleTouchEvent(ev); <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">super</span>.onInterceptTouchEvent(ev); } <span class="hljs-annotation" style="color: rgb(155,136); Box-sizing: border-Box;">boolean</span> <span class="hljs-title" style="Box-sizing: border-Box;">onTouchEvent</span>(MotionEvent ev) { handleTouchEvent(ev); <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">super</span>.onTouchEvent(ev); <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// In case when there is no children interested in handling touch event,we return true from</span> <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// the root view in order to receive subsequent events related to that gesture</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">true</span>; } ... }</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li></ul>
很明显,这里onInterceptTouchEvent和onTouchEvent的处理都是全部交给handleTouchEvent方法统一处理的。
我们再继续看一下handleTouchEvent方法。
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',102);">RootView</span> {</span> ... <span class="hljs-javadoc" style="color: rgb(136,0); Box-sizing: border-Box;">/** * Main catalyst view is responsible for collecting and sending touch events to JS. This method * reacts for an incoming android native touch events ({@link MotionEvent}) and calls into * {@link com.facebook.react.uimanager.events.EventDispatcher} when appropriate. * It uses {@link com.facebook.react.uimanager.TouchTargetManagerHelper#findTouchTargetView} * helper method for figuring out a react view ID in the case of ACTION_DOWN * event (when the gesture starts). */</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">private</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">void</span> <span class="hljs-title" style="Box-sizing: border-Box;">handleTouchEvent</span>(MotionEvent ev) { ... <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">int</span> action = ev.getAction() & MotionEvent.ACTION_MASK; ReactContext reactContext = mReactInstanceManager.getCurrentReactContext(); EventDispatcher eventDispatcher = reactContext.getNativeModule(UIManagerModule.class) .getEventDispatcher(); <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (action == MotionEvent.ACTION_DOWN) { ... mTargetTag = TouchTargetHelper.findTargetTagAndCoordinatesForTouch( ev.getX(),ev.getY(),<span class="hljs-keyword" style="color: rgb(0,mTargetCoordinates); eventDispatcher.dispatchEvent( TouchEvent.obtain( mTargetTag,SystemClock.uptimeMillis(),TouchEventType.START,ev,mTargetCoordinates[<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0</span>],102); Box-sizing: border-Box;">1</span>])); } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">else</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (action == MotionEvent.ACTION_UP) { <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// End of the gesture. We reset target tag to -1 and expect no further event associated with</span> <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// this gesture.</span> eventDispatcher.dispatchEvent( TouchEvent.obtain( mTargetTag,TouchEventType.END,102); Box-sizing: border-Box;">1</span>])); mTargetTag = -<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>; } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (action == MotionEvent.ACTION_MOVE) { <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// Update pointer position for current gesture</span> eventDispatcher.dispatchEvent( TouchEvent.obtain( mTargetTag,TouchEventType.MOVE,136); Box-sizing: border-Box;">if</span> (action == MotionEvent.ACTION_POINTER_DOWN) { <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// New pointer goes down,this can only happen after ACTION_DOWN is sent for the first pointer</span> eventDispatcher.dispatchEvent( TouchEvent.obtain( mTargetTag,136); Box-sizing: border-Box;">if</span> (action == MotionEvent.ACTION_POINTER_UP) { <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// Exactly onw of the pointers goes up</span> eventDispatcher.dispatchEvent( TouchEvent.obtain( mTargetTag,136); Box-sizing: border-Box;">if</span> (action == MotionEvent.ACTION_CANCEL) { dispatchCancelEvent(ev); mTargetTag = -<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>; } } ... }</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li><li style="Box-sizing: border-Box; padding: 0px 5px;">30</li><li style="Box-sizing: border-Box; padding: 0px 5px;">31</li><li style="Box-sizing: border-Box; padding: 0px 5px;">32</li><li style="Box-sizing: border-Box; padding: 0px 5px;">33</li><li style="Box-sizing: border-Box; padding: 0px 5px;">34</li><li style="Box-sizing: border-Box; padding: 0px 5px;">35</li><li style="Box-sizing: border-Box; padding: 0px 5px;">36</li><li style="Box-sizing: border-Box; padding: 0px 5px;">37</li><li style="Box-sizing: border-Box; padding: 0px 5px;">38</li><li style="Box-sizing: border-Box; padding: 0px 5px;">39</li><li style="Box-sizing: border-Box; padding: 0px 5px;">40</li><li style="Box-sizing: border-Box; padding: 0px 5px;">41</li><li style="Box-sizing: border-Box; padding: 0px 5px;">42</li><li style="Box-sizing: border-Box; padding: 0px 5px;">43</li><li style="Box-sizing: border-Box; padding: 0px 5px;">44</li><li style="Box-sizing: border-Box; padding: 0px 5px;">45</li><li style="Box-sizing: border-Box; padding: 0px 5px;">46</li><li style="Box-sizing: border-Box; padding: 0px 5px;">47</li><li style="Box-sizing: border-Box; padding: 0px 5px;">48</li><li style="Box-sizing: border-Box; padding: 0px 5px;">49</li><li style="Box-sizing: border-Box; padding: 0px 5px;">50</li><li style="Box-sizing: border-Box; padding: 0px 5px;">51</li><li style="Box-sizing: border-Box; padding: 0px 5px;">52</li><li style="Box-sizing: border-Box; padding: 0px 5px;">53</li><li style="Box-sizing: border-Box; padding: 0px 5px;">54</li><li style="Box-sizing: border-Box; padding: 0px 5px;">55</li><li style="Box-sizing: border-Box; padding: 0px 5px;">56</li><li style="Box-sizing: border-Box; padding: 0px 5px;">57</li><li style="Box-sizing: border-Box; padding: 0px 5px;">58</li><li style="Box-sizing: border-Box; padding: 0px 5px;">59</li><li style="Box-sizing: border-Box; padding: 0px 5px;">60</li><li style="Box-sizing: border-Box; padding: 0px 5px;">61</li><li style="Box-sizing: border-Box; padding: 0px 5px;">62</li><li style="Box-sizing: border-Box; padding: 0px 5px;">63</li><li style="Box-sizing: border-Box; padding: 0px 5px;">64</li><li style="Box-sizing: border-Box; padding: 0px 5px;">65</li><li style="Box-sizing: border-Box; padding: 0px 5px;">66</li><li style="Box-sizing: border-Box; padding: 0px 5px;">67</li><li style="Box-sizing: border-Box; padding: 0px 5px;">68</li><li style="Box-sizing: border-Box; padding: 0px 5px;">69</li><li style="Box-sizing: border-Box; padding: 0px 5px;">70</li><li style="Box-sizing: border-Box; padding: 0px 5px;">71</li><li style="Box-sizing: border-Box; padding: 0px 5px;">72</li><li style="Box-sizing: border-Box; padding: 0px 5px;">73</li><li style="Box-sizing: border-Box; padding: 0px 5px;">74</li><li style="Box-sizing: border-Box; padding: 0px 5px;">75</li><li style="Box-sizing: border-Box; padding: 0px 5px;">76</li><li style="Box-sizing: border-Box; padding: 0px 5px;">77</li><li style="Box-sizing: border-Box; padding: 0px 5px;">78</li><li style="Box-sizing: border-Box; padding: 0px 5px;">79</li><li style="Box-sizing: border-Box; padding: 0px 5px;">80</li><li style="Box-sizing: border-Box; padding: 0px 5px;">81</li><li style="Box-sizing: border-Box; padding: 0px 5px;">82</li><li style="Box-sizing: border-Box; padding: 0px 5px;">83</li><li style="Box-sizing: border-Box; padding: 0px 5px;">84</li><li style="Box-sizing: border-Box; padding: 0px 5px;">85</li><li style="Box-sizing: border-Box; padding: 0px 5px;">86</li></ul>
代码不是很多,也很好理解。先来看一下注释,意思是ReactRootView负责收集和发送事件给JS,当原生触摸事件响应时通过EventDispatcher类发送,并且在Down事件时通过TouchTargetManagerHelper查找具体被触摸的子View。这里一语道破了触摸事件的核心原理:
所有React组件的触摸事件都是由ReactRootView统一处理,将具体被触摸组件和具体触摸事件发送给Javascript。其中隐藏的一层意思是:React组件自身不用处理触摸事件。
这个很关键,而具体被处理的触摸事件有以下6种,分别是ACTION_DOWN、ACTION_UP、ACTION_MOVE、ACTION_POINTER_DOWN、ACTION_POINTER_UP、ACTION_CANCEL,已经包含了几乎所有的手势动作。
2、Touch事件接收者
接下来,看一下ACTION_DOWN事件时,是如何定位消费Touch事件的React组件的呢?以下图为例:
如果黄色的点表示被触摸的事件点,由于Touch事件是由ReactRootView根节点开始拦截,所以从ReactRootView开始遍历视图树,遍历顺序如下:
1、ReactViewGroup,判断黄点坐标位于ReactViewGroup区域,再判断ReactViewGroup自身或其子视图是否消费Touch事件(通过PointerEvents枚举类,后面详解)。如果自身消费Touch事件,遍历中断,直接返回ReactGroupView;如果子视图消费Touch事件,继续遍历其子视图树;如果不消费Touch事件,返回null。
2、child 1,如果ReactViewGroup的子视图消费Touch事件,则遍历至child 1,首先判断黄点坐标是否位于child 1区域,再判断自身或其子视图是否消费Touch事件。如果child 1是ReactViewGroup类型,同上方1过程处理;如果child 1是非ReactViewGroup类型,即ImageView、TextView等非复合型视图,判断其自身是否消费Touch事件,一般除具有Span属性的TextView外,基本都是消费Touch事件的。如果消费Touch事件,返回child 1,如果不消费Touch事件,返回null。
3、child 2,如果2中child 1不消费Touch事件,继续遍历到child 2,由于触摸点黄点坐标不位于child 2区域内,遍历终止,返回null。
关于视图是否消费Touch事件,通过一个枚举类来说明,代码位于com.facebook.react.uimanager.PointerEvents。
一共有4种枚举类型:
NONE:视图自身或其子视图不消费Touch事件。
Box_NONE:视图自身不消费Touch事件,但其子视图消费。
Box_ONLY:视图自身消费Touch事件,而其子视图不消费。
AUTO:视图自身或其子视图消费Touch事件,但不确定是哪一个。
关于具体如何查找事件消费者的代码主要在com.facebook.react.uimanager.TouchTargetHelper中。
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-javadoc" style="color: rgb(136,0); Box-sizing: border-Box;">/** * Class responsible for identifying which react view should handle a given {@link MotionEvent}. * It uses the event coordinates to traverse the view hierarchy and return a suitable view. */</span> <span class="hljs-keyword" style="color: rgb(0,102);">TouchTargetHelper</span> {</span> ... <span class="hljs-javadoc" style="color: rgb(136,0); Box-sizing: border-Box;">/** * Find touch event target view within the provided container given the coordinates provided * via {@link MotionEvent}. * *<span class="hljs-javadoctag" style="color: rgb(102,102); Box-sizing: border-Box;"> @param</span> eventX the X screen coordinate of the touch location *<span class="hljs-javadoctag" style="color: rgb(102,102); Box-sizing: border-Box;"> @param</span> eventY the Y screen coordinate of the touch location *<span class="hljs-javadoctag" style="color: rgb(102,102); Box-sizing: border-Box;"> @param</span> viewGroup the container view to traverse *<span class="hljs-javadoctag" style="color: rgb(102,102); Box-sizing: border-Box;"> @param</span> viewCoords an out parameter that will return the X,Y value in the target view *<span class="hljs-javadoctag" style="color: rgb(102,102); Box-sizing: border-Box;"> @return</span> the react tag ID of the child view that should handle the event */</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">static</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">int</span> <span class="hljs-title" style="Box-sizing: border-Box;">findTargetTagAndCoordinatesForTouch</span>( <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">float</span> eventX,136); Box-sizing: border-Box;">float</span> eventY,ViewGroup viewGroup,136); Box-sizing: border-Box;">float</span>[] viewCoords) { UiThreadUtil.assertOnUiThread(); <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">int</span> targetTag = viewGroup.getId(); <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// Store eventCoords in array so that they are modified to be relative to the targetView found.</span> viewCoords[<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0</span>] = eventX; viewCoords[<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>] = eventY; View nativeTargetView = findTouchTargetView(viewCoords,viewGroup); <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (nativeTargetView != <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">null</span>) { View reactTargetView = findClosestReactAncestor(nativeTargetView); <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (reactTargetView != <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">null</span>) { targetTag = getTouchTargetForView(reactTargetView,viewCoords[<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>]); } } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> targetTag; } ... }</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li><li style="Box-sizing: border-Box; padding: 0px 5px;">30</li><li style="Box-sizing: border-Box; padding: 0px 5px;">31</li><li style="Box-sizing: border-Box; padding: 0px 5px;">32</li><li style="Box-sizing: border-Box; padding: 0px 5px;">33</li><li style="Box-sizing: border-Box; padding: 0px 5px;">34</li><li style="Box-sizing: border-Box; padding: 0px 5px;">35</li><li style="Box-sizing: border-Box; padding: 0px 5px;">36</li><li style="Box-sizing: border-Box; padding: 0px 5px;">37</li><li style="Box-sizing: border-Box; padding: 0px 5px;">38</li><li style="Box-sizing: border-Box; padding: 0px 5px;">39</li><li style="Box-sizing: border-Box; padding: 0px 5px;">40</li><li style="Box-sizing: border-Box; padding: 0px 5px;">41</li></ul>
具体有三层逻辑:findTouchTargetView、findClosestReactAncestor、getTouchTargetForView,最终是要返回目标View的ID,代码我们一一来看。
1、findTouchTargetView
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',0); Box-sizing: border-Box;">/** * Returns the touch target View that is either viewGroup or one if its descendants. * This is a recursive DFS since view the entire tree must be parsed until the target is found. * If the search does not backtrack,it is possible to follow a branch that cannot be a target * (because of pointerEvents). For example,if both C and E can be the target of an event: * A (pointerEvents: auto) - B (pointerEvents: Box-none) - C (pointerEvents: none) * \ D (pointerEvents: auto) - E (pointerEvents: auto) * If the search goes down the first branch,it would return A as the target,which is incorrect. * NB: This modifies the eventCoords to always be relative to the current viewGroup. When the * method returns,it will contain the eventCoords relative to the targetView found. */</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">static</span> View <span class="hljs-title" style="Box-sizing: border-Box;">findTouchTargetView</span>(<span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">float</span>[] eventCoords,ViewGroup viewGroup) { <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">int</span> childrenCount = viewGroup.getChildCount(); <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">for</span> (<span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">int</span> i = childrenCount - <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>; i >= <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0</span>; i--) { View child = viewGroup.getChildAt(i); PointF childPoint = mTempPoint; <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (isTransformedTouchPointInView(eventCoords[<span class="hljs-number" style="color: rgb(0,eventCoords[<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>],viewGroup,child,childPoint)) { <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// If it is contained within the child View,the childPoint value will contain the view</span> <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// coordinates relative to the child</span> <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// We need to store the existing X,Y for the viewGroup away as it is possible this child</span> <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// will not actually be the target and so we restore them if not</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">float</span> restoreX = eventCoords[<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0</span>]; <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">float</span> restoreY = eventCoords[<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>]; eventCoords[<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0</span>] = childPoint.x; eventCoords[<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>] = childPoint.y; View targetView = findTouchTargetViewWithPointerEvents(eventCoords,child); <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (targetView != <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">null</span>) { <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> targetView; } eventCoords[<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0</span>] = restoreX; eventCoords[<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">1</span>] = restoreY; } } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> viewGroup; }</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li><li style="Box-sizing: border-Box; padding: 0px 5px;">30</li><li style="Box-sizing: border-Box; padding: 0px 5px;">31</li><li style="Box-sizing: border-Box; padding: 0px 5px;">32</li><li style="Box-sizing: border-Box; padding: 0px 5px;">33</li><li style="Box-sizing: border-Box; padding: 0px 5px;">34</li><li style="Box-sizing: border-Box; padding: 0px 5px;">35</li></ul>
循环遍历ReactRootView的视图树,通过isTransformedTouchPointInView方法判断触摸点坐标是否位于当前遍历子视图的区域内。有一点需要特别注意,就是坐标的处理。默认的触摸点坐标是基于ReactRootView的坐标系参照,如果遍历到子视图,需要将触摸点坐标转换成以子视图为坐标系参照的坐标。主要是通过上方代码中的childPoint变量保存和处理的。
触摸点坐标是否位于当前遍历子视图的区域内,通过findTouchTargetViewWithPointerEvents方法,判断当前遍历子视图是否消费Touch事件。
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',0); Box-sizing: border-Box;">/** * Returns the touch target View of the event given,or null if neither the given View nor any of * its descendants are the touch target. */</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">static</span> @Nullable View <span class="hljs-title" style="Box-sizing: border-Box;">findTouchTargetViewWithPointerEvents</span>( <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">float</span> eventCoords[],View view) { PointerEvents pointerEvents = view <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">instanceof</span> ReactPointerEventsView ? ((ReactPointerEventsView) view).getPointerEvents() : PointerEvents.AUTO; <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (pointerEvents == PointerEvents.NONE) { <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">null</span>; } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (pointerEvents == PointerEvents.Box_ONLY) { <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> view; } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (pointerEvents == PointerEvents.Box_NONE) { <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (view <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">instanceof</span> ViewGroup) { View targetView = findTouchTargetView(eventCoords,(ViewGroup) view); <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (targetView != view) { <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> targetView; } ... } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">null</span>; } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (pointerEvents == PointerEvents.AUTO) { <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// Either this view or one of its children is the target</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">instanceof</span> ViewGroup) { <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> findTouchTargetView(eventCoords,(ViewGroup) view); } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> view; } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">else</span> { <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">throw</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">new</span> JSApplicationIllegalArgumentException( <span class="hljs-string" style="color: rgb(0,136,0); Box-sizing: border-Box;">"Unknown pointer event type: "</span> + pointerEvents.toString()); } }</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li><li style="Box-sizing: border-Box; padding: 0px 5px;">30</li><li style="Box-sizing: border-Box; padding: 0px 5px;">31</li><li style="Box-sizing: border-Box; padding: 0px 5px;">32</li><li style="Box-sizing: border-Box; padding: 0px 5px;">33</li><li style="Box-sizing: border-Box; padding: 0px 5px;">34</li></ul>
findTouchTargetViewWithPointerEvents方法对PointerEvents的四种枚举做了相应处理,NONE返回null,Box_ONLY返回当前视图,Box_NONE和AUTO继续遍历,递归调用了findTouchTargetView。
2、findClosestReactAncestor
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">static</span> View <span class="hljs-title" style="Box-sizing: border-Box;">findClosestReactAncestor</span>(View view) { <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">while</span> (view != <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">null</span> && view.getId() <= <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0</span>) { view = (View) view.getParent(); } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> view; }</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li></ul>
由于查找最终是要返回目标视图的ID,如果目标视图的ID非法小于0,则返回其父视图作为替代。此处作用不是很理解,忘解答,感激不尽。
3、getTouchTargetForView
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">int</span> <span class="hljs-title" style="Box-sizing: border-Box;">getTouchTargetForView</span>(View targetView,136); Box-sizing: border-Box;">float</span> eventY) { <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (targetView <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">instanceof</span> ReactCompoundView) { <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// Use coordinates relative to the view,which have been already computed by</span> <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// {@link #findTouchTargetView()}.</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> ((ReactCompoundView) targetView).reactTagForTouch(eventX,eventY); } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> targetView.getId(); }</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li></ul>
这个方法是针对ReactTextView做特殊处理的,由于ReactTextView中可能存在消费Touch事件的Span,如果有则返回其Span的tag值(具体请阅读ReactTextView和ReactTagSpan)。
3、Touch事件发送
代码位于com.facebook.react.uimanager.events.EventDispatcher中,先来看一下EventDispatcher对象的初始化。
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',102);">EventDispatcher</span> <span class="hljs-keyword" style="color: rgb(0,102);">LifecycleEventListener</span> {</span> ... <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">final</span> ReactApplicationContext mReactContext; <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">private</span> @Nullable RCTEventEmitter mRCTEventEmitter; <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">volatile</span> @Nullable ScheduleDispatchFrameCallback mCurrentFrameCallback; <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">public</span> <span class="hljs-title" style="Box-sizing: border-Box;">EventDispatcher</span>(ReactApplicationContext reactContext) { mReactContext = reactContext; mReactContext.addLifecycleEventListener(<span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">this</span>); } <span class="hljs-annotation" style="color: rgb(155,136); Box-sizing: border-Box;">void</span> <span class="hljs-title" style="Box-sizing: border-Box;">onHostResume</span>() { UiThreadUtil.assertOnUiThread(); Assertions.assumeCondition(mCurrentFrameCallback == <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">null</span>); <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (mRCTEventEmitter == <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">null</span>) { mRCTEventEmitter = mReactContext.getJSModule(RCTEventEmitter.class); } mCurrentFrameCallback = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">new</span> ScheduleDispatchFrameCallback(); ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS,mCurrentFrameCallback); } ... }</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li></ul>
EventDispatcher实现了LifecycleEventListener接口,在ReactActivity的各个生命周期执行时回调给EventDispatcher。
onHostResume方法对应Activity的onResume生命周期,主要通过ReactChoreographer单例来post了一个ScheduleDispatchFrameCallback。而ReactChoreographer是对Choreographer的一层封装,这里可以直接看成是Choreographer。Choreographer是一个消息处理器,具体作用不多讲,请参考http://www.jb51.cc/article/p-evfvxvhb-qx.html。
ScheduleDispatchFrameCallback是EventDispatcher的一个内部Choreographer.FrameCallback实现类。接下来看看ScheduleDispatchFrameCallback这个回调类里面处理了哪些东西。
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">private</span> <span class="hljs-class" style="Box-sizing: border-Box;"><span class="hljs-keyword" style="color: rgb(0,102);">ScheduleDispatchFrameCallback</span> <span class="hljs-keyword" style="color: rgb(0,102);">Choreographer</span>.<span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">FrameCallback</span> {</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">boolean</span> mShouldStop = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">false</span>; <span class="hljs-annotation" style="color: rgb(155,157); Box-sizing: border-Box;">@Override</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">void</span> <span class="hljs-title" style="Box-sizing: border-Box;">doFrame</span>(<span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">long</span> frameTimeNanos) { UiThreadUtil.assertOnUiThread(); <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (mShouldStop) { <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span>; } ... <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">try</span> { moveStagedEventsToDispatchQueue(); <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (!mHasDispatchScheduled) { mHasDispatchScheduled = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">true</span>; ... mReactContext.runOnJSQueueThread(mDispatchEventsRunnable); } ReactChoreographer.getInstance() .postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS,136); Box-sizing: border-Box;">this</span>); } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">finally</span> { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">void</span> <span class="hljs-title" style="Box-sizing: border-Box;">stop</span>() { mShouldStop = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">true</span>; } }</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li><li style="Box-sizing: border-Box; padding: 0px 5px;">30</li><li style="Box-sizing: border-Box; padding: 0px 5px;">31</li><li style="Box-sizing: border-Box; padding: 0px 5px;">32</li><li style="Box-sizing: border-Box; padding: 0px 5px;">33</li><li style="Box-sizing: border-Box; padding: 0px 5px;">34</li></ul>
在doFrame的回调方法里面一共做了两件事情:
1、将mDispatchEventsRunnable塞进Javascript线程处理队列中,此线程队列内部有一个Looper消息队列并持有当前线程的Handler消息句柄,最终mDispatchEventsRunnable是通过此Handler塞进消息队列完成处理的。
2、递归调用,再次post了当前ScheduleDispatchFrameCallback,达到一个循环的目的。
接下里,我们看看mDispatchEventsRunnable是如何发送Touch事件的。
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"> <span class="hljs-keyword" style="color: rgb(0,102);">DispatchEventsRunnable</span> <span class="hljs-keyword" style="color: rgb(0,102);">Runnable</span> {</span> <span class="hljs-annotation" style="color: rgb(155,136); Box-sizing: border-Box;">void</span> <span class="hljs-title" style="Box-sizing: border-Box;">run</span>() { ... <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">try</span> { ... mHasDispatchScheduled = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">false</span>; mHasDispatchScheduledCount++; ... <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">synchronized</span> (mEventsToDispatchLock) { ... <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">int</span> eventIdx = <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0</span>; eventIdx < mEventsToDispatchSize; eventIdx++) { Event event = mEventsToDispatch[eventIdx]; <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// Event can be null if it has been coalesced into another event.</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (event == <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">null</span>) { <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">continue</span>; } ... event.dispatch(mRCTEventEmitter); event.dispose(); } clearEventsToDispatch(); mEventCookieToLastEventIdx.clear(); } } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">finally</span> { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } } } </code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li><li style="Box-sizing: border-Box; padding: 0px 5px;">30</li><li style="Box-sizing: border-Box; padding: 0px 5px;">31</li></ul>
DispatchEventsRunnable这个对象的作用,只有一个:循环遍历mEventsToDispatch数组,然后调用event的dispatch方法发送给Javascript。这里涉及到RCTEventEmitter的一个JS组件类,里面有一个receiveTouches(String eventName,WritableArray touches,WritableArray changedIndices)的方法用来与JS交互的,这里不做深入分析,下一篇博客会以此为例详解,敬请关注!
梳理一下,也就是说所有的Touch事件都会预先存入mEventsToDispatch数组里,然后在每次ScheduleDispatchFrameCallback回调后,使用DispatchEventsRunnable最终将Touch事件传递给JS。
而Touch事件如何预先存入mEventsToDispatch数组中,则是通过onInterceptTouchEvent->handleTouchEvent->dispatchEvent->moveStagedEventsToDispatchQueue->addEventToEventsToDispatch的流程运行的,里面还会有一个mEventStaging暂存的过程,比较简单,不再讲解。
4、Touch事件在React-Native中的使用
既然Javascript能够接收到原生native端的几乎所有Touch事件,那么就可以做出很多复杂的交互效果了,以点击(click)事件为例,演示下React-Native的几种交互效果。
4-1 普通触摸效果
点击文本,出现‘Awesome,Clicking!’的点击Toast提示,这是最简单和常用的点击功能,直接使用onPress属性实现。
<code class="hljs PHP has-numbering" style="display: block; padding: 0px; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-function" style="Box-sizing: border-Box;"><span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">function</span> <span class="hljs-title" style="Box-sizing: border-Box;">onClick</span><span class="hljs-params" style="color: rgb(102,102); Box-sizing: border-Box;">()</span>{</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">var</span> ToastAndroid = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">require</span>(<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">'ToastAndroid'</span>) ToastAndroid.show(<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">'Awesome,Clicking!'</span>,ToastAndroid.SHORT); } <span class="hljs-class" style="Box-sizing: border-Box;"><span class="hljs-keyword" style="color: rgb(0,102);">MyProject</span> <span class="hljs-keyword" style="color: rgb(0,102);">Component</span> {</span> render() { <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> ( <View style={styles.container}> <Text onPress={onClick}>Click Me!</Text> </View> ); } }</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li></ul>
4-2 变色触摸效果
使用TouchableHighlight组件实现,点击瞬间或者长按时,可以设定一个颜色视觉差。TouchableHighlight标签必须包裹被点击组件,使用underlayColor属性定义点击时的背景色,同时还有onShowUnderlay和onHideUnderlay两个属性可以监听,背景色显示和隐藏瞬间的事件。
需要注意的一点是onPress属性,必须设置给TouchableHighlight。
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">return</span> ( <View style={styles.container}> <TouchableHighlight underlayColor=<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">'gray'</span> onPress={onClick}> <Text>Click Me!</Text> </TouchableHighlight> </View> ); } } </code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li></ul>
4-3 透明触摸效果
使用TouchableOpacity组件实现,点击瞬间或者长按时,可以设定一个透明度视觉差,一般用于点击图片时使用。使用方式同TouchableHighlight。设定透明度的属性是activeOpacity,如果不设置,默认值为0.2。
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">return</span> ( <View style={styles.container}> <TouchableOpacity activeOpacity={<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">0.3</span>} onPress={onClick}> <Text>Click Me!</Text> </TouchableOpacity> </View> ); } }</code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li></ul>
4-4 原生触摸效果
使用TouchableNativeFeedback组件实现,点击瞬间或者长按时,呈现原生系统的点击效果。使用方式有点特殊,必须且只能包含一个节点,如果是Text这种多节点组件,必须在外面包一层View节点。而且这个功能目前并不完善,快速点击时并不会出现原生点击效果,只有较长时间按住时才正常。
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">return</span> ( <View style={styles.container}> <TouchableNativeFeedback onPress={onClick}> <View> <Text>Click Me!</Text> </View> </TouchableNativeFeedback> </View> ); } } </code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li></ul>
4-5 无反馈触摸效果
使用TouchableWithoutFeedback组件实现,表示触摸时无任何反馈效果(同4-1),使用方式同TouchableHighlight。facebook官方并不推荐使用这个组件,除非你有特殊的原因。
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">return</span> ( <View style={styles.container}> <TouchableWithoutFeedback onPress={onClick}> <Text>Click Me!</Text> </TouchableWithoutFeedback> </View> ); } } </code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li></li></ul>