Weex 事件傳遞的那些事兒

一縷殤流化隱半邊冰霜發表於2017-04-16

前言

在前兩篇文章裡面分別談了Weex如何在Native端初始化的和Weex是如何高效的渲染Native的原生UI的。Native這邊還缺一塊,那就是Native產生的一些事件,是怎麼傳回給JS的。這篇文章就詳細分析這一部分。

目錄

  • 1.Weex的事件型別
  • 2.Weex的事件傳遞

一.Weex的事件型別

在Weex中,目前最新版本中事件總共分為4種型別,通用事件,Appear 事件,Disappear 事件,Page 事件。

在Weex的元件裡面只包含前三種事件,即通用事件,Appear 事件,Disappear 事件。

當WXComponent新增事件的時候,會呼叫以下函式:



- (void)_addEventOnMainThread:(NSString *)addEventName
{
    WX_ADD_EVENT(appear, addAppearEvent)
    WX_ADD_EVENT(disappear, addDisappearEvent)

    WX_ADD_EVENT(click, addClickEvent)
    WX_ADD_EVENT(swipe, addSwipeEvent)
    WX_ADD_EVENT(longpress, addLongPressEvent)

    WX_ADD_EVENT(panstart, addPanStartEvent)
    WX_ADD_EVENT(panmove, addPanMoveEvent)
    WX_ADD_EVENT(panend, addPanEndEvent)

    WX_ADD_EVENT(horizontalpan, addHorizontalPanEvent)
    WX_ADD_EVENT(verticalpan, addVerticalPanEvent)

    WX_ADD_EVENT(touchstart, addTouchStartEvent)
    WX_ADD_EVENT(touchmove, addTouchMoveEvent)
    WX_ADD_EVENT(touchend, addTouchEndEvent)
    WX_ADD_EVENT(touchcancel, addTouchCancelEvent)

    [self addEvent:addEventName];
}複製程式碼

WX_ADD_EVENT是一個巨集:



#define WX_ADD_EVENT(eventName, addSelector) \
if ([addEventName isEqualToString:@#eventName]) {\
    [self addSelector];\
}複製程式碼

即是判斷待新增的事件addEventName的名字和預設支援的事件名字eventName是否一致,如果一致,就執行addSelector方法。

最後會執行一個addEvent:方法,每個元件裡面會可以重寫這個方法。在這個方法裡面做的就是對元件的狀態的標識。

比如WXWebComponent元件裡面的addEvent:方法:


- (void)addEvent:(NSString *)eventName
{
    if ([eventName isEqualToString:@"pagestart"]) {
        _startLoadEvent = YES;
    }
    else if ([eventName isEqualToString:@"pagefinish"]) {
        _finishLoadEvent = YES;
    }
    else if ([eventName isEqualToString:@"error"]) {
        _failLoadEvent = YES;
    }
}複製程式碼

在這個方法裡面即對Web元件裡面的狀態進行了標識。

接下來就看看這幾個元件是怎麼識別事件的觸發的。

1. 通用事件

在WXComponent的定義裡,定義瞭如下和事件相關的變數:


@interface WXComponent ()
{
@package

    BOOL _appearEvent;
    BOOL _disappearEvent;
    UITapGestureRecognizer *_tapGesture;
    NSMutableArray *_swipeGestures;
    UILongPressGestureRecognizer *_longPressGesture;
    UIPanGestureRecognizer *_panGesture;

    BOOL _listenPanStart;
    BOOL _listenPanMove;
    BOOL _listenPanEnd;

    BOOL _listenHorizontalPan;
    BOOL _listenVerticalPan;

    WXTouchGestureRecognizer* _touchGesture;
}複製程式碼

上述變數裡面就包含有4個手勢識別器和1個自定義手勢識別器。所以Weex的通用事件裡面就包含這5種,點選事件,輕掃事件,長按事件,拖動事件,通用觸控事件。

(一)點選事件

首先看點選事件:



    WX_ADD_EVENT(click, addClickEvent)複製程式碼

點選事件是通過上面這個巨集加到指定檢視上的。這個巨集上面提到過了。這裡直接把巨集展開


#define WX_ADD_EVENT(click, addClickEvent) \
if ([addEventName isEqualToString:@“click”]) {\
    [self addClickEvent];\
}複製程式碼

如果addEventName傳進來event的是@“click”,那麼就是執行addClickEvent方法。


- (void)addClickEvent
{
    if (!_tapGesture) {
        _tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onClick:)];
        _tapGesture.delegate = self;
        [self.view addGestureRecognizer:_tapGesture];
    }
}複製程式碼

給當前的檢視增加一個點選手勢,觸發的方法是onClick:方法。



- (void)onClick:(__unused UITapGestureRecognizer *)recognizer
{
    NSMutableDictionary *position = [[NSMutableDictionary alloc] initWithCapacity:4];
    CGFloat scaleFactor = self.weexInstance.pixelScaleFactor;

    if (!CGRectEqualToRect(self.calculatedFrame, CGRectZero)) {
        CGRect frame = [self.view.superview convertRect:self.calculatedFrame toView:self.view.window];
        position[@"x"] = @(frame.origin.x/scaleFactor);
        position[@"y"] = @(frame.origin.y/scaleFactor);
        position[@"width"] = @(frame.size.width/scaleFactor);
        position[@"height"] = @(frame.size.height/scaleFactor);
    }

    [self fireEvent:@"click" params:@{@"position":position}];
}複製程式碼

一旦使用者點選螢幕,就會觸發點選手勢,點選手勢就會執行上述的onClick:方法。在這個方法中,Weex會計算點選出點選到的檢視的座標以及寬高尺寸。

說到這裡就需要提到Weex的座標計算方法了。

(1)計算縮放比例因子

在日常iOS開發中,開發者使用的計算單位是pt。

iPhone5解析度320pt x 568pt
iPhone6解析度375pt x 667pt
iPhone6 Plus解析度414pt x 736pt

由於每個螢幕的ppi不同(ppi:Pixels Per Inch,即每英寸所擁有的畫素數目,螢幕畫素密度。),最終會導致解析度的不同。

Weex 事件傳遞的那些事兒

Weex 事件傳遞的那些事兒

這也就是我們日常說的@1x,@2x,@3x,目前iPhone手機也就3種ppi

@1x,163ppi(iPhone3gs)
@2x,326ppi(iPhone4、4s、5、5s、6,6s,7)
@3x,401ppi(iPhone6+、6s+、7+)

Weex 事件傳遞的那些事兒

px即pixels畫素,1px代表螢幕上一個物理的畫素點。

iPhone5畫素640px x 1136px
iPhone6畫素750px x 1334px
iPhone6 Plus畫素1242px x 2208px

而Weex的開發中,目前都是用的px,而且Weex 對於長度值目前只支援畫素px值,還不支援相對單位(em、rem)

那麼就需要pt和px的換算了。

在Weex的世界裡,定義了一個預設螢幕尺寸,用來適配iOS,Android各種不同大小的螢幕。


// The default screen width which helps us to calculate the real size or scale in different devices.
static const CGFloat WXDefaultScreenWidth = 750.0;複製程式碼

在Weex中定義的預設的螢幕寬度是750,注意是寬度。



+ (CGFloat)defaultPixelScaleFactor
{
    static CGFloat defaultScaleFactor;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        defaultScaleFactor = [self portraitScreenSize].width / WXDefaultScreenWidth;
    });

    return defaultScaleFactor;
}複製程式碼

這裡計算了一個預設的縮放比例因子,portraitScreenSize裡面計算出了螢幕在portrait方向下的大小,即如果方向是landscape,那麼縮放比例因子應該等於WXScreenSize().height / WXDefaultScreenWidth,反之應該等於WXScreenSize().width / WXDefaultScreenWidth。

這裡計算的是pt。

iPhone 4、4s、5、5s、5c、SE的比例因子是0.42666667
iPhone 6、6s、7比例因子是0.5
iPhone 6+、6s+、7+比例因子是0.552

(2)計算檢視的縮放尺寸

計算檢視的縮放尺寸主要在這個方法裡面被計算。



- (void)_calculateFrameWithSuperAbsolutePosition:(CGPoint)superAbsolutePosition
                           gatherDirtyComponents:(NSMutableSet<WXComponent *> *)dirtyComponents
{
    if (!_cssNode->layout.should_update) {
        return;
    }
    _cssNode->layout.should_update = false;
    _isLayoutDirty = NO;

    // 計算檢視的Frame
    CGRect newFrame = CGRectMake(WXRoundPixelValue(_cssNode->layout.position[CSS_LEFT]),
                                 WXRoundPixelValue(_cssNode->layout.position[CSS_TOP]),
                                 WXRoundPixelValue(_cssNode->layout.dimensions[CSS_WIDTH]),
                                 WXRoundPixelValue(_cssNode->layout.dimensions[CSS_HEIGHT]));

    BOOL isFrameChanged = NO;
    // 比較newFrame和_calculatedFrame,第一次_calculatedFrame為CGRectZero
    if (!CGRectEqualToRect(newFrame, _calculatedFrame)) {
        isFrameChanged = YES;
        _calculatedFrame = newFrame;
        [dirtyComponents addObject:self];
    }

    CGPoint newAbsolutePosition = [self computeNewAbsolutePosition:superAbsolutePosition];

    _cssNode->layout.dimensions[CSS_WIDTH] = CSS_UNDEFINED;
    _cssNode->layout.dimensions[CSS_HEIGHT] = CSS_UNDEFINED;
    _cssNode->layout.position[CSS_LEFT] = 0;
    _cssNode->layout.position[CSS_TOP] = 0;

    [self _frameDidCalculated:isFrameChanged];

    for (WXComponent *subcomponent in _subcomponents) {
        [subcomponent _calculateFrameWithSuperAbsolutePosition:newAbsolutePosition gatherDirtyComponents:dirtyComponents];
    }
}複製程式碼

newFrame就是計算出來的縮放過的Frame。

如果嘗試自己手動計算Vue.js上設定的px與實際的檢視座標值相比,你會發現永遠都差一點,雖然偏差不多,但是總有誤差,原因在哪裡呢?就在WXRoundPixelValue這個函式裡面。


CGFloat WXRoundPixelValue(CGFloat value)
{
    CGFloat scale = WXScreenScale();
    return round(value * scale) / scale;
}複製程式碼

WXRoundPixelValue這個函式裡面進行了一次四捨五入的計算,這裡會對精度有所損失,所以就會導致最終Native的元件的座標會偏差一點。

舉個例子:



<style>

    .pic{
        width: 200px;
        height: 200px;
        margin-top: 100px;
        left: 200px;
        background-color: #a88859;
    }

</style>複製程式碼

這裡是一個imageComponent,座標是距離上邊距100px,距離左邊距200px,寬200px,高200px。

假設我們是在iPhone 7+的螢幕上,ppi對應的應該是scale = 3(即@3x)。

按照Weex的上述的計算方法算,那麼對應縮放的px為:


x = 200 * ( 414.0 / 750.0 ) = 110.400000
y = 100 * ( 414.0 / 750.0 ) = 55.200000
width = 200 * ( 414.0 / 750.0 ) = 110.400000
height = 200 * ( 414.0 / 750.0 ) = 110.400000複製程式碼

再轉換成pt:


x = round ( 110.400000 * 3 ) / 3 = 110.333333
y = round ( 55.200000 * 3 ) / 3 = 55.333333
width = round ( 110.400000 * 3 ) / 3 = 110.333333
height = round ( 110.400000 * 3 ) / 3 = 110.333333複製程式碼

如果只是單純的認為是針對750的成比縮放,那麼這裡110.333333 / ( 414.0 / 750.0 ) = 199.87922101,你會發現這個數字距離200還是差了零點幾。精度就是損失在了round函式上了

那麼當前的imageComponent在父檢視裡面的Frame = (110.333333,55.333333,110.333333,110.333333)。

回到onClick:方法裡面。


- (void)onClick:(__unused UITapGestureRecognizer *)recognizer
{
    NSMutableDictionary *position = [[NSMutableDictionary alloc] initWithCapacity:4];
    CGFloat scaleFactor = self.weexInstance.pixelScaleFactor;

    if (!CGRectEqualToRect(self.calculatedFrame, CGRectZero)) {
        CGRect frame = [self.view.superview convertRect:self.calculatedFrame toView:self.view.window];
        position[@"x"] = @(frame.origin.x/scaleFactor);
        position[@"y"] = @(frame.origin.y/scaleFactor);
        position[@"width"] = @(frame.size.width/scaleFactor);
        position[@"height"] = @(frame.size.height/scaleFactor);
    }

    [self fireEvent:@"click" params:@{@"position":position}];
}複製程式碼

如果點選到檢視,就會觸發點選手勢的處理方法,就會進入到上述方法裡。

這裡會計算出點選到的檢視相對於window的絕對座標。


CGRect frame = [self.view.superview convertRect:self.calculatedFrame toView:self.view.window];複製程式碼

上面這句話會進行一個座標轉換。座標系轉換到全域性的window的左邊。

還是按照上面舉的例子,如果imageComponent經過轉換以後,frame = (110.33333333333333, 119.33333333333334, 110.33333333333333, 110.33333333333331),這裡就是y軸的距離發生了變化,因為就加上了navigation + statusBar 的64的高度。

計算出了這個window絕對座標之後,還要還原成相對於750.0寬度的“尺寸”。這裡之所以打引號,就是因為這裡有精度損失,在round函式那裡丟了一些精度。


x = 110.33333333333333 / ( 414.0 / 750.0 ) = 199.8792270531401
y = 119.33333333333334 / ( 414.0 / 750.0 ) = 216.1835748792271
width = 110.33333333333333 / ( 414.0 / 750.0 ) = 199.8792270531401
height = 110.33333333333333 / ( 414.0 / 750.0 ) = 199.8792270531401複製程式碼

上述就是點選以後經過轉換最終得到的座標,這個座標會傳遞給JS。

Weex 事件傳遞的那些事兒

(二)輕掃事件

接著是輕掃事件。



    WX_ADD_EVENT(swipe, addSwipeEvent)複製程式碼

這個巨集和上面點選事件的展開原理一樣,這裡不再贅述。

如果addEventName傳進來event的是@“swipe”,那麼就是執行addSwipeEvent方法。


- (void)addSwipeEvent
{
    if (_swipeGestures) {
        return;
    }

    _swipeGestures = [NSMutableArray arrayWithCapacity:4];


    // 下面的程式碼寫的比較“奇怪”,原因在於UISwipeGestureRecognizer的direction屬性,是一個可選的位掩碼,但是每個手勢識別器又只能處理一個方向的手勢,所以就導致了下面需要生成四個UISwipeGestureRecognizer的手勢識別器。

    SEL selector = @selector(onSwipe:);

    // 新建一個upSwipeRecognizer
    UISwipeGestureRecognizer *upSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self
                                                                                            action:selector];
    upSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
    upSwipeRecognizer.delegate = self;
    [_swipeGestures addObject:upSwipeRecognizer];
    [self.view addGestureRecognizer:upSwipeRecognizer];


    // 新建一個downSwipeRecognizer
    UISwipeGestureRecognizer *downSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self
                                                                                              action:selector];
    downSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionDown;
    downSwipeRecognizer.delegate = self;
    [_swipeGestures addObject:downSwipeRecognizer];
    [self.view addGestureRecognizer:downSwipeRecognizer];

    // 新建一個rightSwipeRecognizer
    UISwipeGestureRecognizer *rightSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self
                                                                                               action:selector];
    rightSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
    rightSwipeRecognizer.delegate = self;
    [_swipeGestures addObject:rightSwipeRecognizer];
    [self.view addGestureRecognizer:rightSwipeRecognizer];

    // 新建一個leftSwipeRecognizer
    UISwipeGestureRecognizer *leftSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self
                                                                                              action:selector];
    leftSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
    leftSwipeRecognizer.delegate = self;
    [_swipeGestures addObject:leftSwipeRecognizer];
    [self.view addGestureRecognizer:leftSwipeRecognizer];
}複製程式碼

上面會新建4個方向上的手勢識別器。因為每個手勢識別器又只能處理一個方向的手勢,所以就導致了需要生成四個UISwipeGestureRecognizer的手勢識別器。

給當前的檢視增加一個輕掃手勢,觸發的方法是onSwipe:方法。



- (void)onSwipe:(UISwipeGestureRecognizer *)gesture
{
    UISwipeGestureRecognizerDirection direction = gesture.direction;

    NSString *directionString;
    switch(direction) {
        case UISwipeGestureRecognizerDirectionLeft:
            directionString = @"left";
            break;
        case UISwipeGestureRecognizerDirectionRight:
            directionString = @"right";
            break;
        case UISwipeGestureRecognizerDirectionUp:
            directionString = @"up";
            break;
        case UISwipeGestureRecognizerDirectionDown:
            directionString = @"down";
            break;
        default:
            directionString = @"unknown";
    }

    CGPoint screenLocation = [gesture locationInView:self.view.window];
    CGPoint pageLoacation = [gesture locationInView:self.weexInstance.rootView];
    NSDictionary *resultTouch = [self touchResultWithScreenLocation:screenLocation pageLocation:pageLoacation identifier:gesture.wx_identifier];
    [self fireEvent:@"swipe" params:@{@"direction":directionString, @"changedTouches":resultTouch ? @[resultTouch] : @[]}];
}複製程式碼

當使用者輕掃以後,會觸發輕掃手勢,於是會在window上和rootView上會獲取到2個座標。


- (NSDictionary *)touchResultWithScreenLocation:(CGPoint)screenLocation pageLocation:(CGPoint)pageLocation identifier:(NSNumber *)identifier
{
    NSMutableDictionary *resultTouch = [[NSMutableDictionary alloc] initWithCapacity:5];
    CGFloat scaleFactor = self.weexInstance.pixelScaleFactor;
    resultTouch[@"screenX"] = @(screenLocation.x/scaleFactor);
    resultTouch[@"screenY"] = @(screenLocation.y/scaleFactor);
    resultTouch[@"pageX"] = @(pageLocation.x/scaleFactor);
    resultTouch[@"pageY"] = @(pageLocation.y/scaleFactor);
    resultTouch[@"identifier"] = identifier;

    return resultTouch;
}複製程式碼

screenLocation和pageLocation兩個座標點,還是會根據縮放比例還原成相對於750寬度的頁面的座標。screenLocation的X值和Y值、pageLocation的X值和Y值分別封裝到resultTouch字典裡。




@implementation UIGestureRecognizer (WXGesture)

- (NSNumber *)wx_identifier
{
    NSNumber *identifier = objc_getAssociatedObject(self, _cmd);
    if (!identifier) {
        static NSUInteger _gestureIdentifier;
        identifier = @(_gestureIdentifier++);
        self.wx_identifier = identifier;
    }

    return identifier;
}

- (void)setWx_identifier:(NSNumber *)wx_identifier
{
    objc_setAssociatedObject(self, @selector(wx_identifier), wx_identifier, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end複製程式碼

最後resultTouch裡面還包含一個identifier的引數,這個identifier是一個全域性唯一的NSUInteger。wx_identifier被關聯到了各個手勢識別器上了。

Weex 事件傳遞的那些事兒

(三)長按事件

接著是輕掃事件。



    WX_ADD_EVENT(longpress, addLongPressEvent)複製程式碼

這個巨集和上面點選事件的展開原理一樣,這裡不再贅述。

如果addEventName傳進來event的是@“longpress”,那麼就是執行addLongPressEvent方法。


- (void)addLongPressEvent
{
    if (!_longPressGesture) {
        _longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onLongPress:)];
        _longPressGesture.delegate = self;
        [self.view addGestureRecognizer:_longPressGesture];
    }
}複製程式碼

給當前的檢視增加一個長按手勢,觸發的方法是onLongPress:方法。



- (void)onLongPress:(UILongPressGestureRecognizer *)gesture
{
    if (gesture.state == UIGestureRecognizerStateBegan) {
        CGPoint screenLocation = [gesture locationInView:self.view.window];
        CGPoint pageLoacation = [gesture locationInView:self.weexInstance.rootView];
        NSDictionary *resultTouch = [self touchResultWithScreenLocation:screenLocation pageLocation:pageLoacation identifier:gesture.wx_identifier];
        [self fireEvent:@"longpress" params:@{@"changedTouches":resultTouch ? @[resultTouch] : @[]}];
    } else if (gesture.state == UIGestureRecognizerStateEnded) {
        gesture.wx_identifier = nil;
    }
}複製程式碼

長按手勢傳給JS的引數和輕掃的引數changedTouches幾乎一致。在長按手勢開始的時候就傳遞給JS兩個Point,screenLocation和pageLoacation,以及手勢的wx_identifier。這部分和輕掃手勢基本一樣,不多贅述。

Weex 事件傳遞的那些事兒

(四)拖動事件

拖動事件在Weex裡面包含5個事件。分別對應著拖動的5種狀態:拖動開始,拖動中,拖動結束,水平拖動,豎直拖動。


    WX_ADD_EVENT(panstart, addPanStartEvent)
    WX_ADD_EVENT(panmove, addPanMoveEvent)
    WX_ADD_EVENT(panend, addPanEndEvent)
    WX_ADD_EVENT(horizontalpan, addHorizontalPanEvent)
    WX_ADD_EVENT(verticalpan, addVerticalPanEvent)複製程式碼

為了區分上面5種狀態,Weex還對每個狀態增加了一個BOOL變數來判斷當前的狀態。分別如下:



    BOOL _listenPanStart;
    BOOL _listenPanMove;
    BOOL _listenPanEnd;
    BOOL _listenHorizontalPan;
    BOOL _listenVerticalPan;複製程式碼

通過巨集增加的5個事件,實質都是執行了addPanGesture方法,只不過每個狀態的事件都會跟對應的BOOL變數。



- (void)addPanStartEvent
{
   // 拖動開始
    _listenPanStart = YES;
    [self addPanGesture];
}

- (void)addPanMoveEvent
{
   // 拖動中
    _listenPanMove = YES;
    [self addPanGesture];
}

- (void)addPanEndEvent
{
   // 拖動結束
    _listenPanEnd = YES;
    [self addPanGesture];
}

- (void)addHorizontalPanEvent
{
   // 水平拖動
    _listenHorizontalPan = YES;
    [self addPanGesture];
}

- (void)addVerticalPanEvent
{
   // 豎直拖動
    _listenVerticalPan = YES;
    [self addPanGesture];
}複製程式碼

最終都是呼叫addPanGesture方法:



- (void)addPanGesture
{
    if (!_panGesture) {
        _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPan:)];
        _panGesture.delegate = self;
        [self.view addGestureRecognizer:_panGesture];
    }
}複製程式碼

給當前的檢視增加一個拖動手勢,觸發的方法是onPan:方法。


- (void)onPan:(UIPanGestureRecognizer *)gesture
{
    CGPoint screenLocation = [gesture locationInView:self.view.window];
    CGPoint pageLoacation = [gesture locationInView:self.weexInstance.rootView];
    NSString *eventName;
    NSString *state = @"";
    NSDictionary *resultTouch = [self touchResultWithScreenLocation:screenLocation pageLocation:pageLoacation identifier:gesture.wx_identifier];

    if (gesture.state == UIGestureRecognizerStateBegan) {
        if (_listenPanStart) {
            eventName = @"panstart";
        }
        state = @"start";
    } else if (gesture.state == UIGestureRecognizerStateEnded) {
        if (_listenPanEnd) {
            eventName = @"panend";
        }
        state = @"end";
        gesture.wx_identifier = nil;
    } else if (gesture.state == UIGestureRecognizerStateChanged) {
        if (_listenPanMove) {
             eventName = @"panmove";
        }
        state = @"move";
    }


    CGPoint translation = [_panGesture translationInView:self.view];

    if (_listenHorizontalPan && fabs(translation.y) <= fabs(translation.x))="" {="" [self="" fireevent:@"horizontalpan"="" params:@{@"state":state,="" @"changedtouches":resulttouch="" ?="" @[resulttouch]="" :="" @[]}];="" }="" if="" (_listenverticalpan="" &&="" fabs(translation.y)=""> fabs(translation.x)) {
        [self fireEvent:@"verticalpan" params:@{@"state":state, @"changedTouches":resultTouch ? @[resultTouch] : @[]}];
    }

    if (eventName) {
        [self fireEvent:eventName params:@{@"changedTouches":resultTouch ? @[resultTouch] : @[]}];
    }
}=>複製程式碼

拖動事件最終傳給JS的resultTouch字典和前兩個手勢的原理一樣,也是需要傳入兩個Point,screenLocation和pageLoacation,這裡不再贅述。

Weex 事件傳遞的那些事兒

根據_listenPanStart,_listenPanEnd,_listenPanMove判斷當前的狀態,並生成與之對應的eventName和state字串。

根據_panGesture在當前檢視上拖動形成的有方向的向量,進行判斷當前拖動的方向。

Weex 事件傳遞的那些事兒

(五)通用觸控事件

最後就是通用的觸控事件。

Weex裡面對每個Component都新建了一個手勢識別器。


@interface WXTouchGestureRecognizer : UIGestureRecognizer

@property (nonatomic, assign) BOOL listenTouchStart;
@property (nonatomic, assign) BOOL listenTouchMove;
@property (nonatomic, assign) BOOL listenTouchEnd;
@property (nonatomic, assign) BOOL listenTouchCancel;
@property (nonatomic, assign) BOOL listenPseudoTouch;
{
    __weak WXComponent *_component;
    NSUInteger _touchIdentifier;
}

- (instancetype)initWithComponent:(WXComponent *)component NS_DESIGNATED_INITIALIZER;

@end複製程式碼

WXTouchGestureRecognizer是繼承自UIGestureRecognizer。裡面就5個BOOL。分別表示5種狀態。

WXTouchGestureRecognizer會弱引用當前的WXComponent,並且也依舊有touchIdentifier。

Weex通過以下4個巨集註冊觸控事件方法。


    WX_ADD_EVENT(touchstart, addTouchStartEvent)
    WX_ADD_EVENT(touchmove, addTouchMoveEvent)
    WX_ADD_EVENT(touchend, addTouchEndEvent)
    WX_ADD_EVENT(touchcancel, addTouchCancelEvent)複製程式碼

通過上述巨集增加的4個事件,實質都是改變每個狀態的事件都會跟對應的BOOL變數。


- (void)addTouchStartEvent
{
    self.touchGesture.listenTouchStart = YES;
}

- (void)addTouchMoveEvent
{
    self.touchGesture.listenTouchMove = YES;
}

- (void)addTouchEndEvent
{
    self.touchGesture.listenTouchEnd = YES;
}

- (void)addTouchCancelEvent
{
    self.touchGesture.listenTouchCancel = YES;
}複製程式碼

當使用者開始觸控螢幕,在螢幕上移動,手指從螢幕上結束觸控,取消觸控,分別都會觸發touchesBegan:,touchesMoved:,touchesEnded:,touchesCancelled:方法。


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];

    if (_listenTouchStart) {
        [self fireTouchEvent:@"touchstart" withTouches:touches];
    }
    if(_listenPseudoTouch) {
        NSMutableDictionary *styles = [_component getPseudoClassStyles:@"active"];
        [_component updatePseudoClassStyles:styles];
    }

}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];

    if (_listenTouchMove) {
        [self fireTouchEvent:@"touchmove" withTouches:touches];
    }
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{

    [super touchesEnded:touches withEvent:event];

    if (_listenTouchEnd) {
        [self fireTouchEvent:@"touchend" withTouches:touches];
    }
    if(_listenPseudoTouch) {
        [self recoveryPseudoStyles:_component.styles];
    }

}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled:touches withEvent:event];

    if (_listenTouchCancel) {
        [self fireTouchEvent:@"touchcancel" withTouches:touches];
    }
    if(_listenPseudoTouch) {
        [self recoveryPseudoStyles:_component.styles];
    }
}複製程式碼

上述的4個事件裡面實質都是在呼叫fireTouchEvent:withTouches:方法:


- (void)fireTouchEvent:(NSString *)eventName withTouches:(NSSet<UITouch *> *)touches
{
    NSMutableArray *resultTouches = [NSMutableArray new];

    for (UITouch *touch in touches) {
        CGPoint screenLocation = [touch locationInView:touch.window];
        CGPoint pageLocation = [touch locationInView:_component.weexInstance.rootView];
        if (!touch.wx_identifier) {
            touch.wx_identifier = @(_touchIdentifier++);
        }
        NSDictionary *resultTouch = [_component touchResultWithScreenLocation:screenLocation pageLocation:pageLocation identifier:touch.wx_identifier];
        [resultTouches addObject:resultTouch];
    }

    [_component fireEvent:eventName params:@{@"changedTouches":resultTouches ?: @[]}];
}複製程式碼

最終這個方法和前3個手勢一樣,都需要給resultTouches傳入2個Point和1個wx_identifier。原理一致。

至於座標如何傳遞給JS見第二章。

2. Appear 事件

如果一個位於某個可滾動區域內的元件被繫結了 appear 事件,那麼當這個元件的狀態變為在螢幕上可見時,該事件將被觸發。

所以繫結了Appear 事件的都是可以滾動的檢視。

Weex 事件傳遞的那些事兒



    WX_ADD_EVENT(appear, addAppearEvent)複製程式碼

通過上述的巨集給可以滾動的檢視增加Appear 事件。也就是當前檢視執行addAppearEvent方法。


- (void)addAppearEvent
{
    _appearEvent = YES;
    [self.ancestorScroller addScrollToListener:self];
}複製程式碼

在Weex的每個元件裡面都有2個BOOL記錄著當前_appearEvent和_disappearEvent的狀態。


    BOOL _appearEvent;
    BOOL _disappearEvent;複製程式碼

當增加對應的事件的時候,就會把對應的BOOL變成YES。


- (id<WXScrollerProtocol>)ancestorScroller
{
    if(!_ancestorScroller) {
        WXComponent *supercomponent = self.supercomponent;
        while (supercomponent) {
            if([supercomponent conformsToProtocol:@protocol(WXScrollerProtocol)]) {
                _ancestorScroller = (id<WXScrollerProtocol>)supercomponent;
                break;
            }
            supercomponent = supercomponent.supercomponent;
        }
    }

    return _ancestorScroller;
}複製程式碼

由於Appear 事件和 Disappear 事件都必須要求是滾動檢視,所以這裡會遍歷當前檢視的supercomponent,直到找到一個遵循WXScrollerProtocol的supercomponent。



- (void)addScrollToListener:(WXComponent *)target
{
    BOOL has = NO;
    for (WXScrollToTarget *targetData in self.listenerArray) {
        if (targetData.target == target) {
            has = YES;
            break;
        }
    }
    if (!has) {
        WXScrollToTarget *scrollTarget = [[WXScrollToTarget alloc] init];
        scrollTarget.target = target;
        scrollTarget.hasAppear = NO;
        [self.listenerArray addObject:scrollTarget];
    }
}複製程式碼

在滾動檢視裡麵包含有一個listenerArray,陣列裡面裝的都是被監聽的物件。新增進這個陣列會先判斷當前是否有相同的WXScrollToTarget,避免重複新增,如果沒有重複的就新建一個WXScrollToTarget,再新增進listenerArray中。



@interface WXScrollToTarget : NSObject
@property (nonatomic, weak)   WXComponent *target;
@property (nonatomic, assign) BOOL hasAppear;
@end複製程式碼

WXScrollToTarget是一個普通的物件,裡面弱引用了當前需要監聽的WXComponent,以及一個BOOL變數記錄當前是否Appear了。

當滾動檢視滾動的時候,就會觸發scrollViewDidScroll:方法。


- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    //apply block which are registered
    WXSDKInstance *instance = self.weexInstance;
    if ([self.ref isEqualToString:WX_SDK_ROOT_REF] &&
        [self isKindOfClass:[WXScrollerComponent class]]) {
        if (instance.onScroll) {
            instance.onScroll(scrollView.contentOffset);
        }
    }

    if (_lastContentOffset.x > scrollView.contentOffset.x) {
        _direction = @"right";
    } else if (_lastContentOffset.x < scrollView.contentOffset.x) {
        _direction = @"left";
    } else if(_lastContentOffset.y > scrollView.contentOffset.y) {
        _direction = @"down";
    } else if(_lastContentOffset.y < scrollView.contentOffset.y) {
        _direction = @"up";
    }

    _lastContentOffset = scrollView.contentOffset;

    // check sticky
    [self adjustSticky];
    [self handleLoadMore];
    [self handleAppear];

    if (self.onScroll) {
        self.onScroll(scrollView);
    }
}複製程式碼

在上面的方法中[self handleAppear]就是觸發了判斷是否Appear了。


- (void)handleAppear
{
    if (![self isViewLoaded]) {
        return;
    }
    UIScrollView *scrollView = (UIScrollView *)self.view;
    CGFloat vx = scrollView.contentInset.left + scrollView.contentOffset.x;
    CGFloat vy = scrollView.contentInset.top + scrollView.contentOffset.y;
    CGFloat vw = scrollView.frame.size.width - scrollView.contentInset.left - scrollView.contentInset.right;
    CGFloat vh = scrollView.frame.size.height - scrollView.contentInset.top - scrollView.contentInset.bottom;
    CGRect scrollRect = CGRectMake(vx, vy, vw, vh);;

    // notify action for appear
    for(WXScrollToTarget *target in self.listenerArray){
        [self scrollToTarget:target scrollRect:scrollRect];
    }
}複製程式碼

上面這個方法會把listenerArray陣列裡面的每個WXScrollToTarget物件都呼叫scrollToTarget:scrollRect:方法。根據當前滾動的情況傳入一個CGRect,這個CGRect就是當前滾動到那個矩形區域的座標資訊以及寬和高。


- (void)scrollToTarget:(WXScrollToTarget *)target scrollRect:(CGRect)rect
{
    WXComponent *component = target.target;
    if (![component isViewLoaded]) {
        return;
    }

    // 計算出當前的可見區域的頂部座標
    CGFloat ctop;
    if (component && component->_view && component->_view.superview) {
        ctop = [component->_view.superview convertPoint:component->_view.frame.origin toView:_view].y;
    } else {
        ctop = 0.0;
    }
    // 計算出當前的可見區域的底部座標
    CGFloat cbottom = ctop + CGRectGetHeight(component.calculatedFrame);
    // 計算出當前的可見區域的左邊界座標
    CGFloat cleft;
    if (component && component->_view && component->_view.superview) {
        cleft = [component->_view.superview convertPoint:component->_view.frame.origin toView:_view].x;
    } else {
        cleft = 0.0;
    }
    // 計算出當前的可見區域的右邊界座標
    CGFloat cright = cleft + CGRectGetWidth(component.calculatedFrame);

    // 獲取傳入的滾動的區域
    CGFloat vtop = CGRectGetMinY(rect), vbottom = CGRectGetMaxY(rect), vleft = CGRectGetMinX(rect), vright = CGRectGetMaxX(rect);

    // 判斷當前可見區域是否包含在傳入的滾動區域內,如果在,並且監聽了appear事件,就觸發appear事件,否則如果監聽了disappear事件就觸發disappear事件
    if(cbottom > vtop && ctop <= vbottom && cleft <= vright && cright > vleft){
        if(!target.hasAppear && component){
            target.hasAppear = YES;
            // 如果當前監聽了appear,就觸發appear事件
            if (component->_appearEvent) {
                [component fireEvent:@"appear" params:_direction ? @{@"direction":_direction} : nil];
            }
        }
    } else {
        if(target.hasAppear && component){
            target.hasAppear = NO;
            // 如果當前監聽了disappear,就觸發disappear事件
            if(component->_disappearEvent){
                [component fireEvent:@"disappear" params:_direction ? @{@"direction":_direction} : nil];
            }
        }
    }
}複製程式碼

Weex 事件傳遞的那些事兒

scrollToTarget:scrollRect:方法的核心就是拿當前可視區域和傳入的滾動區域進行對比,如果在該區域內,且監聽了appear事件,就會觸發appear事件,如果不在該區域內,且監聽了disappear事件,就會觸發disappear事件。

3. Disappear 事件

如果一個位於某個可滾動區域內的元件被繫結了 disappear 事件,那麼當這個元件被滑出螢幕變為不可見狀態時,該事件將被觸發。

同理,繫結了Disappear 事件的都是可以滾動的檢視。

Weex 事件傳遞的那些事兒



    WX_ADD_EVENT(disappear, addDisappearEvent)複製程式碼

通過上述的巨集給可以滾動的檢視增加Disappear 事件。也就是當前檢視執行addDisappearEvent方法。


- (void)addDisappearEvent
{
    _disappearEvent = YES;
    [self.ancestorScroller addScrollToListener:self];
}複製程式碼

接下去的和Appear 事件的原理就一模一樣了。

4. Page 事件

暫時Weex只支援 iOS 和 Android,H5 暫不支援。

Weex 通過 viewappear 和 viewdisappear 事件提供了簡單的頁面狀態管理能力。

viewappear 事件會在頁面就要顯示或配置的任何頁面動畫被執行前觸發,例如,當呼叫 navigator 模組的 push 方法時,該事件將會在開啟新頁面時被觸發。viewdisappear 事件會在頁面就要關閉時被觸發。

與元件Component的 appear 和 disappear 事件不同的是,viewappear 和 viewdisappear 事件關注的是整個頁面的狀態,所以它們必須繫結到頁面的根元素上。

特殊情況下,這兩個事件也能被繫結到非根元素的body元件上,例如wxc-navpage元件。

舉個例子:


- (void)_updateInstanceState:(WXState)state
{
    if (_instance && _instance.state != state) {
        _instance.state = state;

        if (state == WeexInstanceAppear) {
            [[WXSDKManager bridgeMgr] fireEvent:_instance.instanceId ref:WX_SDK_ROOT_REF type:@"viewappear" params:nil domChanges:nil];
        } else if (state == WeexInstanceDisappear) {
            [[WXSDKManager bridgeMgr] fireEvent:_instance.instanceId ref:WX_SDK_ROOT_REF type:@"viewdisappear" params:nil domChanges:nil];
        }
    }
}複製程式碼

比如在WXBaseViewController裡面,有這樣一個更新當前Instance狀態的方法,這個方法裡面就會觸發 viewappear 和 viewdisappear 事件。

其中WX_SDK_ROOT_REF就是_root


#define WX_SDK_ROOT_REF     @"_root"複製程式碼

上述更新狀態的方法同樣出現在WXEmbedComponent元件中。


- (void)_updateState:(WXState)state
{
    if (_renderFinished && _embedInstance && _embedInstance.state != state) {
        _embedInstance.state = state;

        if (state == WeexInstanceAppear) {
            [self setNavigationWithStyles:self.embedInstance.naviBarStyles];
            [[WXSDKManager bridgeMgr] fireEvent:self.embedInstance.instanceId ref:WX_SDK_ROOT_REF type:@"viewappear" params:nil domChanges:nil];
        }
        else if (state == WeexInstanceDisappear) {
            [[WXSDKManager bridgeMgr] fireEvent:self.embedInstance.instanceId ref:WX_SDK_ROOT_REF type:@"viewdisappear" params:nil domChanges:nil];
        }
    }
}複製程式碼

二.Weex的事件傳遞

在Weex中,iOS Native把事件傳遞給JS目前只有2種方式,一是Module模組的callback,二是通過Component元件自定義的通知事件。

(1)callback

Weex 事件傳遞的那些事兒

在WXModuleProtocol中定義了2種可以callback給JS的閉包。



/**
 * @abstract the module callback , result can be string or dictionary.
 * @discussion callback data to js, the id of callback function will be removed to save memory.
 */
typedef void (^WXModuleCallback)(id result);

/**
 * @abstract the module callback , result can be string or dictionary.
 * @discussion callback data to js, you can specify the keepAlive parameter to keep callback function id keepalive or not. If the keepAlive is true, it won't be removed until instance destroyed, so you can call it repetitious.
 */
typedef void (^WXModuleKeepAliveCallback)(id result, BOOL keepAlive);複製程式碼

兩個閉包都可以callback把data傳遞迴給JS,data可以是字串或者字典。

這兩個閉包的區別在於:

  1. WXModuleCallback用於Module元件,為了節約記憶體,該回撥只能回撥通知JS一次,之後會被釋放,多用於一次結果。
  2. WXModuleKeepAliveCallback同樣是用於Module元件,但是該回撥可以設定是否為多次回撥型別,如果設定了keepAlive,那麼可以進行持續監聽變化,多次回撥,並返回給 JS。

在Weex中使用WXModuleCallback回撥,很多情況是把狀態回撥給JS,比如成功或者失敗的狀態,還有一些出錯的資訊回撥給JS。

比如在WXStorageModule中



- (void)setItem:(NSString *)key value:(NSString *)value callback:(WXModuleCallback)callback
{
    if ([self checkInput:key]) {
        callback(@{@"result":@"failed",@"data":@"key must a string or number!"});
        return;
    }
    if ([self checkInput:value]) {
        callback(@{@"result":@"failed",@"data":@"value must a string or number!"});
        return;
    }

    if ([key isKindOfClass:[NSNumber class]]) {
        key = [((NSNumber *)key) stringValue];
    }

    if ([value isKindOfClass:[NSNumber class]]) {
        value = [((NSNumber *)value) stringValue];
    }

    if ([WXUtility isBlankString:key]) {
        callback(@{@"result":@"failed",@"data":@"invalid_param"});
        return ;
    }
    [self setObject:value forKey:key persistent:NO callback:callback];
}複製程式碼

在呼叫setItem:value:callback:方法裡面,如果setKey-value的時候失敗了,會把錯誤資訊通過WXModuleCallback回撥給JS。

當然,如果呼叫儲存模組WXStorageModule的某些查詢資訊的方法:



- (void)length:(WXModuleCallback)callback
{
    callback(@{@"result":@"success",@"data":@([[WXStorageModule memory] count])});
}

- (void)getAllKeys:(WXModuleCallback)callback
{
    callback(@{@"result":@"success",@"data":[WXStorageModule memory].allKeys});
}複製程式碼

length:和getAllKeys:方法呼叫成功,會把成功的狀態和資料通過WXModuleCallback回撥給JS。

在Weex中使用了WXModuleKeepAliveCallback的模組總共只有以下4個:

WXDomModule,WXStreamModule,WXWebSocketModule,WXGlobalEventModule

在WXDomModule模組中,JS呼叫獲取Component元件的位置資訊和寬高資訊的時候,需要把這些座標和尺寸資訊回撥給JS,不過這裡雖然用到了WXModuleKeepAliveCallback,但是keepAlive是false,並沒有用到多次回撥的功能。

在WXStreamModule模組中,由於這是一個傳輸流的模組,所以肯定需要用到WXModuleKeepAliveCallback,需要持續不斷的監聽資料的變化,並把進度回撥給JS,這裡用到了keepAlive。WXStreamModule模組中也會用到WXModuleCallback,WXModuleCallback會即時把各個狀態回撥給JS。

在WXWebSocketModule模組中



@interface WXWebSocketModule()

@property(nonatomic,copy)WXModuleKeepAliveCallback errorCallBack;
@property(nonatomic,copy)WXModuleKeepAliveCallback messageCallBack;
@property(nonatomic,copy)WXModuleKeepAliveCallback openCallBack;
@property(nonatomic,copy)WXModuleKeepAliveCallback closeCallBack;

@end複製程式碼

用到了4個WXModuleKeepAliveCallback回撥,這4個callback分別是把error錯誤資訊,message收到的資料,open開啟連結的狀態,close關閉連結的狀態,持續的回撥給JS。

在WXGlobalEventModule模組中,有一個fireGlobalEvent:方法。



- (void)fireGlobalEvent:(NSNotification *)notification
{
    NSDictionary * userInfo = notification.userInfo;
    NSString * userWeexInstanceId = userInfo[@"weexInstance"];

    WXSDKInstance * userWeexInstance = [WXSDKManager instanceForID:userWeexInstanceId];
   // 防止userInstanceId存在,但是instance實際已經被銷燬了
    if (!userWeexInstanceId || userWeexInstance == weexInstance) {

        for (WXModuleKeepAliveCallback callback in _eventCallback[notification.name]) {
            callback(userInfo[@"param"], true);
        }
    }
}複製程式碼

開發者可以通過WXGlobalEventModule進行全域性的通知,在userInfo裡面可以夾帶weexInstance的引數。native是不需要關心userWeexInstanceId,這個引數是給JS用的。

Native開發者只需要在用到了WXGlobalEventModule的模組里加上事件的監聽者,然後傳送全域性通知即可。userInfo[@"param"]會被回撥給JS。

(2)fireEvent:params:domChanges:

Weex 事件傳遞的那些事兒

在開頭我們介紹的Weex事件的4種型別,通用事件,Appear 事件,Disappear 事件,Page 事件,全部都是通過fireEvent:params:domChanges:這種方式,Native觸發事件之後,Native把引數傳遞給JS的。

在WXComponent裡面定義了2個可以給JS傳送訊息的方法:



/**
 * @abstract Fire an event to the component in Javascript.
 *
 * @param eventName The name of the event to fire
 * @param params The parameters to fire with
 **/
- (void)fireEvent:(NSString *)eventName params:(nullable NSDictionary *)params;

/**
 * @abstract Fire an event to the component and tell Javascript which value has been changed. 
 * Used for two-way data binding.
 *
 * @param eventName The name of the event to fire
 * @param params The parameters to fire with
 * @param domChanges The values has been changed, used for two-way data binding.
 **/
- (void)fireEvent:(NSString *)eventName params:(nullable NSDictionary *)params domChanges:(nullable NSDictionary *)domChanges;複製程式碼

這兩個方法的區別就在於最後一個domChanges的引數,有這個引數的方法主要多用於Weex的Native和JS的雙向資料繫結。



- (void)fireEvent:(NSString *)eventName params:(NSDictionary *)params
{
    [self fireEvent:eventName params:params domChanges:nil];
}

- (void)fireEvent:(NSString *)eventName params:(NSDictionary *)params domChanges:(NSDictionary *)domChanges
{
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    NSTimeInterval timeSp = [[NSDate date] timeIntervalSince1970] * 1000;
    [dict setObject:@(timeSp) forKey:@"timestamp"];
    if (params) {
        [dict addEntriesFromDictionary:params];
    }

    [[WXSDKManager bridgeMgr] fireEvent:self.weexInstance.instanceId ref:self.ref type:eventName params:dict domChanges:domChanges];
}複製程式碼

上述就是兩個方法的具體實現。可以看到fireEvent:params:方法就是呼叫了fireEvent:params:domChanges:方法,只不過最後的domChanges引數傳了nil。

在fireEvent:params:domChanges:方法中會對params字典做了一次加工,加上了timestamp的鍵值。最終還是會呼叫WXBridgeManager 裡面的fireEvent:ref: type:params:domChanges:方法。

在WXBridgeManager中具體實現了上述的兩個方法。



- (void)fireEvent:(NSString *)instanceId ref:(NSString *)ref type:(NSString *)type params:(NSDictionary *)params
{
    [self fireEvent:instanceId ref:ref type:type params:params domChanges:nil];
}

- (void)fireEvent:(NSString *)instanceId ref:(NSString *)ref type:(NSString *)type params:(NSDictionary *)params domChanges:(NSDictionary *)domChanges
{
    if (!type || !ref) {
        WXLogError(@"Event type and component ref should not be nil");
        return;
    }

    NSArray *args = @[ref, type, params?:@{}, domChanges?:@{}];
    WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];

    WXCallJSMethod *method = [[WXCallJSMethod alloc] initWithModuleName:nil methodName:@"fireEvent" arguments:args instance:instance];
    [self callJsMethod:method];
}複製程式碼

入參ref, type, params, domChanges封裝到最終的args引數陣列裡面,最後會封裝出WXCallJSMethod方法,通過WXBridgeManager的callJsMethod呼叫到JS的fireEvent方法。

Weex 事件傳遞的那些事兒

這裡可以舉個例子:

假設一個場景,使用者點選了一張圖片,於是就會改變label上的一段文字。

首先圖片是imageComponent,使用者點選會觸發該Component的onclick:方法

元件裡面會呼叫fireEvent:params:方法:


[self fireEvent:@"click" params:@{@"position":position}];複製程式碼

最終通過fireEvent:params:domChanges:方法,傳送給JS的引數字典大概如下:


args:(
    0,
        (
                {
            args =             (
                3,
                click,
                                {
                    position =                     {
                        height = "199.8792270531401";
                        width = "199.8792270531401";
                        x = "274.7584541062802";
                        y = "115.9420289855072";
                    };
                    timestamp = "1489932655404.133";
                },
                                {
                }
            );
            method = fireEvent;
            module = "";
        }
    )
)複製程式碼

JSFramework收到了fireEvent方法呼叫以後,處理完,知道label需要更新,於是又會開始call Native,呼叫Native的方法。呼叫Native的callNative方法,發過來的引數如下:



(
        {
        args =         (
            4,
                        {
                value = "\U56fe\U7247\U88ab\U70b9\U51fb";
            }
        );
        method = updateAttrs;
        module = dom;
    }
)複製程式碼

最終會呼叫Dom的updateAttrs方法,會去更新id為4的value,id為4對應的就是label,更新它的值就是重新整理label。

接著JSFramework還會繼續呼叫Native的callNative方法,發過來的引數如下:



(
        {
        args =         (
        );
        method = updateFinish;
        module = dom;
    }
)複製程式碼

呼叫Dom的updateFinish方法,即頁面重新整理完畢。

最後

至此,Weex從View的建立,到渲染,產生事件回撥JSFramework,這一系列的流程原始碼都解析完成了。

Weex 事件傳遞的那些事兒

中間涉及到了3個子執行緒,mainThread,com.taobao.weex.component,com.taobao.weex.bridge,分別是UI主執行緒,DOM執行緒,JSbridge執行緒。

Native端目前還差神祕的JSFramework的原始碼解析。請大家多多指點。

相關文章