編輯:關於Android編程
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
// this gesture.
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
mTargetTag,
SystemClock.uptimeMillis(),
TouchEventType.END,
ev,
mTargetCoordinates[0],
mTargetCoordinates[1]));
mTargetTag = -1;
} else if (action == MotionEvent.ACTION_MOVE) {
// Update pointer position for current gesture
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
mTargetTag,
SystemClock.uptimeMillis(),
TouchEventType.MOVE,
ev,
mTargetCoordinates[0],
mTargetCoordinates[1]));
} else if (action == MotionEvent.ACTION_POINTER_DOWN) {
// New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
mTargetTag,
SystemClock.uptimeMillis(),
TouchEventType.START,
ev,
mTargetCoordinates[0],
mTargetCoordinates[1]));
} else if (action == MotionEvent.ACTION_POINTER_UP) {
// Exactly onw of the pointers goes up
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
mTargetTag,
SystemClock.uptimeMillis(),
TouchEventType.END,
ev,
mTargetCoordinates[0],
mTargetCoordinates[1]));
} else if (action == MotionEvent.ACTION_CANCEL) {
dispatchCancelEvent(ev);
mTargetTag = -1;
}
}
...
}
代碼不是很多,也很好理解。先來看一下注釋,意思是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<喎?/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPqOsxdC2z7vGtePX+LHqzrvT2jxzdHJvbmc+UmVhY3RWaWV3R3JvdXA8L3N0cm9uZz7H+NPyo6zU2cXQts88c3Ryb25nPlJlYWN0Vmlld0dyb3VwPC9zdHJvbmc+19TJ7bvyxuTX08rTzbzKx7fxz/u30TxzdHJvbmc+VG91Y2g8L3N0cm9uZz7Kwrz+o6jNqLn9PHN0cm9uZz5Qb2ludGVyRXZlbnRzPC9zdHJvbmc+w7a+2cDgo6y688Pmz+q94qOpoaPI57n719TJ7c/7t9E8c3Ryb25nPlRvdWNoPC9zdHJvbmc+ysK8/qOssenA+tbQts+jrNaxvdO3tbvYPHN0cm9uZz5SZWFjdEdyb3VwVmlldzwvc3Ryb25nPqO7yOe5+9fTytPNvM/7t9E8c3Ryb25nPlRvdWNoPC9zdHJvbmc+ysK8/qOsvMzQ+LHpwPrG5NfTytPNvMr3o7vI57n7srvP+7fRPHN0cm9uZz5Ub3VjaDwvc3Ryb25nPsrCvP6jrLe1u9g8c3Ryb25nPm51bGw8L3N0cm9uZz6hozwvcD4NCjxwPjxzdHJvbmc+MqGiY2hpbGQgMTwvc3Ryb25nPqOsyOe5+zxzdHJvbmc+UmVhY3RWaWV3R3JvdXA8L3N0cm9uZz61xNfTytPNvM/7t9E8c3Ryb25nPlRvdWNoPC9zdHJvbmc+ysK8/qOs1PKx6cD61sE8c3Ryb25nPmNoaWxkIDE8L3N0cm9uZz6jrMrXz8jF0LbPu8a149f4serKx7fxzrvT2jxzdHJvbmc+Y2hpbGQgMTwvc3Ryb25nPsf40/KjrNTZxdC2z9fUye278sbk19PK0828yse38c/7t9E8c3Ryb25nPlRvdWNoPC9zdHJvbmc+ysK8/qGjyOe5+zxzdHJvbmc+Y2hpbGQgMTwvc3Ryb25nPsrHPHN0cm9uZz5SZWFjdFZpZXdHcm91cDwvc3Ryb25nPsDg0M2jrM2syc+3vTxzdHJvbmc+MTwvc3Ryb25nPrn9s8y0psDto7vI57n7PHN0cm9uZz5jaGlsZCAxPC9zdHJvbmc+yse3xzxzdHJvbmc+UmVhY3RWaWV3R3JvdXA8L3N0cm9uZz7A4NDNo6y8tDxzdHJvbmc+SW1hZ2VWaWV3PC9zdHJvbmc+oaI8c3Ryb25nPlRleHRWaWV3PC9zdHJvbmc+tci3x7i0us/QzcrTzbyjrMXQts/G5NfUye3Kx7fxz/u30TxzdHJvbmc+VG91Y2g8L3N0cm9uZz7Kwrz+o6zSu7Djs/2+39PQPHN0cm9uZz5TcGFuPC9zdHJvbmc+yvTQ1LXEPHN0cm9uZz5UZXh0Vmlldzwvc3Ryb25nPs3io6y7+bG+trzKx8/7t9E8c3Ryb25nPlRvdWNoPC9zdHJvbmc+ysK8/rXEoaPI57n7z/u30TxzdHJvbmc+VG91Y2g8L3N0cm9uZz7Kwrz+o6y3tbvYPHN0cm9uZz5jaGlsZCAxPC9zdHJvbmc+o6zI57n7srvP+7fRPHN0cm9uZz5Ub3VjaDwvc3Ryb25nPsrCvP6jrLe1u9g8c3Ryb25nPm51bGw8L3N0cm9uZz6hozwvcD4NCjxwPjxzdHJvbmc+M6GiY2hpbGQgMjwvc3Ryb25nPqOsyOe5+zxzdHJvbmc+Mjwvc3Ryb25nPtbQPHN0cm9uZz5jaGlsZCAxPC9zdHJvbmc+srvP+7fRPHN0cm9uZz5Ub3VjaDwvc3Ryb25nPsrCvP6jrLzM0Pix6cD6tb08c3Ryb25nPmNoaWxkIDI8L3N0cm9uZz6jrNPJ09q0pcP+teO7xrXj1/ix6rK7zrvT2jxzdHJvbmc+Y2hpbGQgMjwvc3Ryb25nPsf40/LE2qOssenA+tbV1rmjrLe1u9g8c3Ryb25nPm51bGw8L3N0cm9uZz6hozwvcD4NCjxwPrnY09rK0828yse38c/7t9E8c3Ryb25nPlRvdWNoPC9zdHJvbmc+ysK8/qOszai5/dK7uPbDtr7ZwODAtMu1w/ejrLT6wuvOu9PaPHN0cm9uZz5jb20uZmFjZWJvb2sucmVhY3QudWltYW5hZ2VyLlBvaW50ZXJFdmVudHM8L3N0cm9uZz6hozxiciAvPg0K0ru5stPQNNbWw7a+2cDg0M2jujwvcD4NCjxwPjxzdHJvbmc+Tk9ORTwvc3Ryb25nPqO6ytPNvNfUye278sbk19PK0828srvP+7fRPHN0cm9uZz5Ub3VjaDwvc3Ryb25nPsrCvP6hozxiciAvPg0KPHN0cm9uZz5CT1hfTk9ORTwvc3Ryb25nPqO6ytPNvNfUye2yu8/7t9E8c3Ryb25nPlRvdWNoPC9zdHJvbmc+ysK8/qOstavG5NfTytPNvM/7t9GhozxiciAvPg0KPHN0cm9uZz5CT1hfT05MWTwvc3Ryb25nPqO6ytPNvNfUye3P+7fRPHN0cm9uZz5Ub3VjaDwvc3Ryb25nPsrCvP6jrLb4xuTX08rTzbyyu8/7t9GhozxiciAvPg0KPHN0cm9uZz5BVVRPPC9zdHJvbmc+o7rK082819TJ7bvyxuTX08rTzbzP+7fRPHN0cm9uZz5Ub3VjaDwvc3Ryb25nPsrCvP6jrLWrsrvIt7aoysfExNK7uPahozwvcD4NCjxwPrnY09q+38zlyOe6zrLp1dLKwrz+z/u30dXftcS0+sLr1vfSqtTaPHN0cm9uZz5jb20uZmFjZWJvb2sucmVhY3QudWltYW5hZ2VyLlRvdWNoVGFyZ2V0SGVscGVyPC9zdHJvbmc+1tChozwvcD4NCjxwcmUgY2xhc3M9"brush:java;">
具體有三層邏輯:findTouchTargetView、findClosestReactAncestor、getTouchTargetForView,最終是要返回目標View的ID,代碼我們一一來看。 1、findTouchTargetView 循環遍歷ReactRootView 的視圖樹,通過isTransformedTouchPointInView方法判斷觸摸點坐標是否位於當前遍歷子視圖的區域內。有一點需要特別注意,就是坐標的處理。默認的觸摸點坐標是基於ReactRootView 的坐標系參照,如果遍歷到子視圖,需要將觸摸點坐標轉換成以子視圖為坐標系參照的坐標。主要是通過上方代碼中的childPoint變量保存和處理的。 findTouchTargetViewWithPointerEvents方法對PointerEvents的四種枚舉做了相應處理,NONE返回null,BOX_ONLY返回當前視圖,BOX_NONE和AUTO繼續遍歷,遞歸調用了findTouchTargetView。 2、findClosestReactAncestor 由於查找最終是要返回目標視圖的ID,如果目標視圖的ID非法小於0,則返回其父視圖作為替代。此處作用不是很理解,忘解答,感激不盡。 3、getTouchTargetForView 這個方法是針對ReactTextView做特殊處理的,由於ReactTextView中可能存在消費Touch事件的Span,如果有則返回其Span的tag值(具體請閱讀ReactTextView和ReactTagSpan)。 3、Touch事件發送 代碼位於com.facebook.react.uimanager.events.EventDispatcher中,先來看一下EventDispatcher對象的初始化。 EventDispatcher實現了LifecycleEventListener接口,在ReactActivity的各個生命周期執行時回調給EventDispatcher。 onHostResume方法對應Activity的onResume生命周期,主要通過ReactChoreographer單例來post了一個ScheduleDispatchFrameCallback。而ReactChoreographer是對Choreographer的一層封裝,這裡可以直接看成是Choreographer。Choreographer是一個消息處理器 。 ScheduleDispatchFrameCallback是EventDispatcher的一個內部Choreographer.FrameCallback實現類。接下來看看ScheduleDispatchFrameCallback這個回調類裡面處理了哪些東西。 在doFrame的回調方法裡面一共做了兩件事情: 接下裡,我們看看mDispatchEventsRunnable是如何發送Touch事件的。 DispatchEventsRunnable這個對象的作用,只有一個:循環遍歷mEventsToDispatch數組,然後調用event的dispatch方法發送給Javascript。這裡涉及到RCTEventEmitter的一個JS組件類,裡面有一個receiveEvent(int targetTag, String eventName, WritableMap event)的方法用來與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屬性實現。 4-2 變色觸摸效果 使用TouchableHighlight組件實現,點擊瞬間或者長按時,可以設定一個顏色視覺差。TouchableHighlight標簽必須包裹被點擊組件,使用underlayColor屬性定義點擊時的背景色,同時還有onShowUnderlay和onHideUnderlay兩個屬性可以監聽,背景色顯示和隱藏瞬間的事件。 需要注意的一點是onPress屬性,必須設置給TouchableHighlight。 4-3 透明觸摸效果 使用TouchableOpacity組件實現,點擊瞬間或者長按時,可以設定一個透明度視覺差,一般用於點擊圖片時使用。使用方式同TouchableHighlight。設定透明度的屬性是activeOpacity,如果不設置,默認值為0.2。 4-4 原生觸摸效果 使用TouchableNativeFeedback組件實現,點擊瞬間或者長按時,呈現原生系統的點擊效果。使用方式有點特殊,必須且只能包含一個節點,如果是Text這種多節點組件,必須在外面包一層View節點。而且這個功能目前並不完善,快速點擊時並不會出現原生點擊效果,只有較長時間按住時才正常。 4-5 無反饋觸摸效果 使用TouchableWithoutFeedback組件實現,表示觸摸時無任何反饋效果(同4-1),使用方式同TouchableHighlight。facebook官方並不推薦使用這個組件,除非你有特殊的原因。/**
* 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.
*/
public class TouchTargetHelper {
...
/**
* Find touch event target view within the provided container given the coordinates provided
* via {@link MotionEvent}.
*
* @param eventX the X screen coordinate of the touch location
* @param eventY the Y screen coordinate of the touch location
* @param viewGroup the container view to traverse
* @param viewCoords an out parameter that will return the X,Y value in the target view
* @return the react tag ID of the child view that should handle the event
*/
public static int findTargetTagAndCoordinatesForTouch(
float eventX,
float eventY,
ViewGroup viewGroup,
float[] viewCoords) {
UiThreadUtil.assertOnUiThread();
int targetTag = viewGroup.getId();
// Store eventCoords in array so that they are modified to be relative to the targetView found.
viewCoords[0] = eventX;
viewCoords[1] = eventY;
View nativeTargetView = findTouchTargetView(viewCoords, viewGroup);
if (nativeTargetView != null) {
View reactTargetView = findClosestReactAncestor(nativeTargetView);
if (reactTargetView != null) {
targetTag = getTouchTargetForView(reactTargetView, viewCoords[0], viewCoords[1]);
}
}
return targetTag;
}
...
}
/**
* 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.
*/
private static View findTouchTargetView(float[] eventCoords, ViewGroup viewGroup) {
int childrenCount = viewGroup.getChildCount();
for (int i = childrenCount - 1; i >= 0; i--) {
View child = viewGroup.getChildAt(i);
PointF childPoint = mTempPoint;
if (isTransformedTouchPointInView(eventCoords[0], eventCoords[1], viewGroup, child, childPoint)) {
// If it is contained within the child View, the childPoint value will contain the view
// coordinates relative to the child
// We need to store the existing X,Y for the viewGroup away as it is possible this child
// will not actually be the target and so we restore them if not
float restoreX = eventCoords[0];
float restoreY = eventCoords[1];
eventCoords[0] = childPoint.x;
eventCoords[1] = childPoint.y;
View targetView = findTouchTargetViewWithPointerEvents(eventCoords, child);
if (targetView != null) {
return targetView;
}
eventCoords[0] = restoreX;
eventCoords[1] = restoreY;
}
}
return viewGroup;
}
觸摸點坐標是否位於當前遍歷子視圖的區域內,通過findTouchTargetViewWithPointerEvents方法,判斷當前遍歷子視圖是否消費Touch事件。
/**
* 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.
*/
private static @Nullable View findTouchTargetViewWithPointerEvents(
float eventCoords[], View view) {
PointerEvents pointerEvents = view instanceof ReactPointerEventsView ?
((ReactPointerEventsView) view).getPointerEvents() : PointerEvents.AUTO;
if (pointerEvents == PointerEvents.NONE) {
return null;
} else if (pointerEvents == PointerEvents.BOX_ONLY) {
return view;
} else if (pointerEvents == PointerEvents.BOX_NONE) {
if (view instanceof ViewGroup) {
View targetView = findTouchTargetView(eventCoords, (ViewGroup) view);
if (targetView != view) {
return targetView;
}
...
}
return null;
} else if (pointerEvents == PointerEvents.AUTO) {
// Either this view or one of its children is the target
if (view instanceof ViewGroup) {
return findTouchTargetView(eventCoords, (ViewGroup) view);
}
return view;
} else {
throw new JSApplicationIllegalArgumentException(
"Unknown pointer event type: " + pointerEvents.toString());
}
}
private static View findClosestReactAncestor(View view) {
while (view != null && view.getId() <= 0) {
view = (View) view.getParent();
}
return view;
}
private static int getTouchTargetForView(View targetView, float eventX, float eventY) {
if (targetView instanceof ReactCompoundView) {
// Use coordinates relative to the view, which have been already computed by
// {@link #findTouchTargetView()}.
return ((ReactCompoundView) targetView).reactTagForTouch(eventX, eventY);
}
return targetView.getId();
}
public class EventDispatcher implements LifecycleEventListener {
...
private final ReactApplicationContext mReactContext;
private @Nullable RCTEventEmitter mRCTEventEmitter;
private volatile @Nullable ScheduleDispatchFrameCallback mCurrentFrameCallback;
public EventDispatcher(ReactApplicationContext reactContext) {
mReactContext = reactContext;
mReactContext.addLifecycleEventListener(this);
}
@Override
public void onHostResume() {
UiThreadUtil.assertOnUiThread();
Assertions.assumeCondition(mCurrentFrameCallback == null);
if (mRCTEventEmitter == null) {
mRCTEventEmitter = mReactContext.getJSModule(RCTEventEmitter.class);
}
mCurrentFrameCallback = new ScheduleDispatchFrameCallback();
ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, mCurrentFrameCallback);
}
...
}
private class ScheduleDispatchFrameCallback implements Choreographer.FrameCallback {
private boolean mShouldStop = false;
@Override
public void doFrame(long frameTimeNanos) {
UiThreadUtil.assertOnUiThread();
if (mShouldStop) {
return;
}
...
try {
moveStagedEventsToDispatchQueue();
if (!mHasDispatchScheduled) {
mHasDispatchScheduled = true;
...
mReactContext.runOnJSQueueThread(mDispatchEventsRunnable);
}
ReactChoreographer.getInstance()
.postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, this);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
public void stop() {
mShouldStop = true;
}
}
1、將mDispatchEventsRunnable塞進Javascript處理線程的隊列中,其內部是通過Handler消息機制關聯到主線程做處理的。
2、遞歸調用,再次post了當前ScheduleDispatchFrameCallback,達到一個循環的目的。
private class DispatchEventsRunnable implements Runnable {
@Override
public void run() {
...
try {
...
mHasDispatchScheduled = false;
mHasDispatchScheduledCount++;
...
synchronized (mEventsToDispatchLock) {
...
for (int eventIdx = 0; eventIdx < mEventsToDispatchSize; eventIdx++) {
Event event = mEventsToDispatch[eventIdx];
// Event can be null if it has been coalesced into another event.
if (event == null) {
continue;
}
...
event.dispatch(mRCTEventEmitter);
event.dispose();
}
clearEventsToDispatch();
mEventCookieToLastEventIdx.clear();
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
}
function onClick(){
var ToastAndroid = require('ToastAndroid')
ToastAndroid.show('Awesome, Clicking!', ToastAndroid.SHORT);
}
class MyProject extends Component {
render() {
return (
function onClick(){
var ToastAndroid = require('ToastAndroid')
ToastAndroid.show('Awesome, Clicking!', ToastAndroid.SHORT);
}
class MyProject extends Component {
render() {
return (
function onClick(){
var ToastAndroid = require('ToastAndroid')
ToastAndroid.show('Awesome, Clicking!', ToastAndroid.SHORT);
}
class MyProject extends Component {
render() {
return (
function onClick(){
var ToastAndroid = require('ToastAndroid')
ToastAndroid.show('Awesome, Clicking!', ToastAndroid.SHORT);
}
class MyProject extends Component {
render() {
return (
function onClick(){
var ToastAndroid = require('ToastAndroid')
ToastAndroid.show('Awesome, Clicking!', ToastAndroid.SHORT);
}
class MyProject extends Component {
render() {
return (
Android實現在線播放音樂 2014年3月10日 hello,小伙伴們,3月份珊珊來遲的第一篇博客,最近小巫在找工作,加上又生病了,就沒有太多精力去寫博客了。今天
時間悄悄的走,轉眼來實習已經三個月了,三個月的時間,小編慢慢的成長著,從剛開始的電商項目到現在的車段子項目,小編在走過一個又一個項目的同時,走過了一個又一個戰勝自己的奇跡
看到很多書中都沒有對PreferenceActivity做介紹,而我正好又在項目中用到,所以就把自己的使用的在這總結一下,也方便日後查找。 PerferenceActiv
前言:本來我是做電視應用的,但是因為公司要出手機,人員緊張,所以就抽調我去支援一下,誰叫俺是雷鋒呢!我做的一個功能就是處理手機中的應用ICON,處理無非就是美化一下,重新