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的根视图被初始化及添加的。
public abstract class ReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
...
protected ReactRootView createRootView() {
return new ReactRootView(this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) {
// Get permission to show redbox in dev builds.
if (!Settings.canDrawOverlays(this)) {
Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(serviceIntent);
FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);
Toast.makeText(this, REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();
}
}
mReactInstanceManager = createReactInstanceManager();
ReactRootView mReactRootView = createRootView();
mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(), getLaunchOptions());
setContentView(mReactRootView);
}
...
}
在ReactActivity的onCreate这个生命周期里,直接实列化,然后作为当前Window的ContentView,也就可以认为其是所有React-Native组件的根视图。
熟悉Android触摸事件机制的,应该知道视图树中,触摸事件是逐级传递的,每个视图(View)中有两个接收和处理Touch事件的方法,分别是onInterceptTouchEvent和onTouchEvent,这两个方法的区别为:
onInterceptTouchEvent的传递层级是由父视图向子视图,顾名思义,通常用作事件拦截。
onTouchEvent的传递层级是由子视图向父视图,通常用作事件处理。
我们来看一下ReactRootView的事件接收和处理。
public class ReactRootView extends SizeMonitoringFrameLayout implements RootView {
...
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
handleTouchEvent(ev);
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
handleTouchEvent(ev);
super.onTouchEvent(ev);
// In case when there is no children interested in handling touch event, we return true from
// the root view in order to receive subsequent events related to that gesture
return true;
}
...
}
很明显,这里onInterceptTouchEvent和onTouchEvent的处理都是全部交给handleTouchEvent方法统一处理的。
我们再继续看一下handleTouchEvent方法。
public class ReactRootView extends SizeMonitoringFrameLayout implements RootView {
...
/**
* 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).
*/
private void handleTouchEvent(MotionEvent ev) {
...
int action = ev.getAction() & MotionEvent.ACTION_MASK;
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
EventDispatcher eventDispatcher = reactContext.getNativeModule(UIManagerModule.class)
.getEventDispatcher();
if (action == MotionEvent.ACTION_DOWN) {
...
mTargetTag = TouchTargetHelper.findTargetTagAndCoordinatesForTouch(
ev.getX(),
ev.getY(),
this,
mTargetCoordinates);
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
mTargetTag,
SystemClock.uptimeMillis(),
TouchEventType.START,
ev,
mTargetCoordinates[0],
mTargetCoordinates[1]));
} else if (action == MotionEvent.ACTION_UP) {
// End of the gesture. We reset target tag to -1 and expect no further event associated with