前言
在前兩篇文章里面分別談了Weex如何在Native端初始化的和Weex是如何高效的渲染Native的原生UI的叔汁。Native這邊還缺一塊隅忿,那就是Native產(chǎn)生的一些事件迫靖,是怎么傳回給JS的懊蒸。這篇文章就詳細(xì)分析這一部分拇泛。
目錄
- 1.Weex的事件類型
- 2.Weex的事件傳遞
一.Weex的事件類型
在Weex中滨巴,目前最新版本中事件總共分為4種類型思灌,通用事件,Appear 事件恭取,Disappear 事件泰偿,Page 事件。
在Weex的組件里面只包含前三種事件蜈垮,即通用事件耗跛,Appear 事件,Disappear 事件攒发。
當(dāng)WXComponent添加事件的時候调塌,會調(diào)用以下函數(shù):
- (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的名字和默認(rèn)支持的事件名字eventName是否一致,如果一致惠猿,就執(zhí)行addSelector方法羔砾。
最后會執(zhí)行一個addEvent:方法,每個組件里面會可以重寫這個方法偶妖。在這個方法里面做的就是對組件的狀態(tài)的標(biāo)識姜凄。
比如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組件里面的狀態(tài)進(jìn)行了標(biāo)識。
接下來就看看這幾個組件是怎么識別事件的觸發(fā)的趾访。
1. 通用事件
在WXComponent的定義里态秧,定義了如下和事件相關(guān)的變量:
@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種扼鞋,點(diǎn)擊事件申鱼,輕掃事件,長按事件云头,拖動事件捐友,通用觸摸事件。
(一)點(diǎn)擊事件
首先看點(diǎn)擊事件:
WX_ADD_EVENT(click, addClickEvent)
點(diǎn)擊事件是通過上面這個宏加到指定視圖上的盘寡。這個宏上面提到過了楚殿。這里直接把宏展開
#define WX_ADD_EVENT(click, addClickEvent) \
if ([addEventName isEqualToString:@“click”]) {\
[self addClickEvent];\
}
如果addEventName傳進(jìn)來event的是@“click”,那么就是執(zhí)行addClickEvent方法竿痰。
- (void)addClickEvent
{
if (!_tapGesture) {
_tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onClick:)];
_tapGesture.delegate = self;
[self.view addGestureRecognizer:_tapGesture];
}
}
給當(dāng)前的視圖增加一個點(diǎn)擊手勢脆粥,觸發(fā)的方法是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}];
}
一旦用戶點(diǎn)擊屏幕影涉,就會觸發(fā)點(diǎn)擊手勢变隔,點(diǎn)擊手勢就會執(zhí)行上述的onClick:方法。在這個方法中蟹倾,Weex會計算點(diǎn)擊出點(diǎn)擊到的視圖的坐標(biāo)以及寬高尺寸匣缘。
說到這里就需要提到Weex的坐標(biāo)計算方法了猖闪。
(1)計算縮放比例因子
在日常iOS開發(fā)中,開發(fā)者使用的計算單位是pt肌厨。
iPhone5分辨率320pt x 568pt
iPhone6分辨率375pt x 667pt
iPhone6 Plus分辨率414pt x 736pt
由于每個屏幕的ppi不同(ppi:Pixels Per Inch培慌,即每英寸所擁有的像素數(shù)目,屏幕像素密度柑爸。)吵护,最終會導(dǎo)致分辨率的不同。
這也就是我們?nèi)粘Uf的@1x表鳍,@2x馅而,@3x,目前iPhone手機(jī)也就3種ppi
@1x譬圣,163ppi(iPhone3gs)
@2x瓮恭,326ppi(iPhone4、4s厘熟、5屯蹦、5s、6盯漂,6s颇玷,7)
@3x,401ppi(iPhone6+就缆、6s+帖渠、7+)
px即pixels像素,1px代表屏幕上一個物理的像素點(diǎn)竭宰。
iPhone5像素640px x 1136px
iPhone6像素750px x 1334px
iPhone6 Plus像素1242px x 2208px
而Weex的開發(fā)中空郊,目前都是用的px,而且Weex 對于長度值目前只支持像素px值切揭,還不支持相對單位(em狞甚、rem)。
那么就需要pt和px的換算了廓旬。
在Weex的世界里哼审,定義了一個默認(rèn)屏幕尺寸,用來適配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中定義的默認(rèn)的屏幕寬度是750,注意是寬度励背。
+ (CGFloat)defaultPixelScaleFactor
{
static CGFloat defaultScaleFactor;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultScaleFactor = [self portraitScreenSize].width / WXDefaultScreenWidth;
});
return defaultScaleFactor;
}
這里計算了一個默認(rèn)的縮放比例因子春霍,portraitScreenSize里面計算出了屏幕在portrait方向下的大小,即如果方向是landscape叶眉,那么縮放比例因子應(yīng)該等于WXScreenSize().height / WXDefaultScreenWidth址儒,反之應(yīng)該等于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上設(shè)置的px與實際的視圖坐標(biāo)值相比彼念,你會發(fā)現(xiàn)永遠(yuǎn)都差一點(diǎn),雖然偏差不多浅萧,但是總有誤差逐沙,原因在哪里呢?就在WXRoundPixelValue這個函數(shù)里面洼畅。
CGFloat WXRoundPixelValue(CGFloat value)
{
CGFloat scale = WXScreenScale();
return round(value * scale) / scale;
}
WXRoundPixelValue這個函數(shù)里面進(jìn)行了一次四舍五入的計算吩案,這里會對精度有所損失,所以就會導(dǎo)致最終Native的組件的坐標(biāo)會偏差一點(diǎn)帝簇。
舉個例子:
<style>
.pic{
width: 200px;
height: 200px;
margin-top: 100px;
left: 200px;
background-color: #a88859;
}
</style>
這里是一個imageComponent徘郭,坐標(biāo)是距離上邊距100px,距離左邊距200px丧肴,寬200px残揉,高200px。
假設(shè)我們是在iPhone 7+的屏幕上芋浮,ppi對應(yīng)的應(yīng)該是scale = 3(即@3x)抱环。
按照Weex的上述的計算方法算,那么對應(yīng)縮放的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
再轉(zhuǎn)換成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
如果只是單純的認(rèn)為是針對750的成比縮放纸巷,那么這里110.333333 / ( 414.0 / 750.0 ) = 199.87922101镇草,你會發(fā)現(xiàn)這個數(shù)字距離200還是差了零點(diǎn)幾。精度就是損失在了round函數(shù)上了
那么當(dāng)前的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}];
}
如果點(diǎn)擊到視圖黔夭,就會觸發(fā)點(diǎn)擊手勢的處理方法,就會進(jìn)入到上述方法里羽嫡。
這里會計算出點(diǎn)擊到的視圖相對于window的絕對坐標(biāo)本姥。
CGRect frame = [self.view.superview convertRect:self.calculatedFrame toView:self.view.window];
上面這句話會進(jìn)行一個坐標(biāo)轉(zhuǎn)換。坐標(biāo)系轉(zhuǎn)換到全局的window的左邊杭棵。
還是按照上面舉的例子婚惫,如果imageComponent經(jīng)過轉(zhuǎn)換以后,frame = (110.33333333333333, 119.33333333333334, 110.33333333333333, 110.33333333333331)魂爪,這里就是y軸的距離發(fā)生了變化先舷,因為就加上了navigation + statusBar 的64的高度。
計算出了這個window絕對坐標(biāo)之后滓侍,還要還原成相對于750.0寬度的“尺寸”蒋川。這里之所以打引號,就是因為這里有精度損失撩笆,在round函數(shù)那里丟了一些精度捺球。
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
上述就是點(diǎn)擊以后經(jīng)過轉(zhuǎn)換最終得到的坐標(biāo),這個坐標(biāo)會傳遞給JS夕冲。
(二)輕掃事件
接著是輕掃事件氮兵。
WX_ADD_EVENT(swipe, addSwipeEvent)
這個宏和上面點(diǎn)擊事件的展開原理一樣,這里不再贅述歹鱼。
如果addEventName傳進(jìn)來event的是@“swipe”泣栈,那么就是執(zhí)行addSwipeEvent方法。
- (void)addSwipeEvent
{
if (_swipeGestures) {
return;
}
_swipeGestures = [NSMutableArray arrayWithCapacity:4];
// 下面的代碼寫的比較“奇怪”醉冤,原因在于UISwipeGestureRecognizer的direction屬性秩霍,是一個可選的位掩碼,但是每個手勢識別器又只能處理一個方向的手勢蚁阳,所以就導(dǎo)致了下面需要生成四個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個方向上的手勢識別器。因為每個手勢識別器又只能處理一個方向的手勢螺捐,所以就導(dǎo)致了需要生成四個UISwipeGestureRecognizer的手勢識別器颠悬。
給當(dāng)前的視圖增加一個輕掃手勢,觸發(fā)的方法是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] : @[]}];
}
當(dāng)用戶輕掃以后赔癌,會觸發(fā)輕掃手勢,于是會在window上和rootView上會獲取到2個坐標(biāo)澜沟。
- (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兩個坐標(biāo)點(diǎn)灾票,還是會根據(jù)縮放比例還原成相對于750寬度的頁面的坐標(biāo)。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的參數(shù)既们,這個identifier是一個全局唯一的NSUInteger。wx_identifier被關(guān)聯(lián)到了各個手勢識別器上了啥纸。
(三)長按事件
接著是輕掃事件。
WX_ADD_EVENT(longpress, addLongPressEvent)
這個宏和上面點(diǎn)擊事件的展開原理一樣婴氮,這里不再贅述斯棒。
如果addEventName傳進(jìn)來event的是@“l(fā)ongpress”,那么就是執(zhí)行addLongPressEvent方法主经。
- (void)addLongPressEvent
{
if (!_longPressGesture) {
_longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onLongPress:)];
_longPressGesture.delegate = self;
[self.view addGestureRecognizer:_longPressGesture];
}
}
給當(dāng)前的視圖增加一個長按手勢荣暮,觸發(fā)的方法是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的參數(shù)和輕掃的參數(shù)changedTouches幾乎一致旨怠。在長按手勢開始的時候就傳遞給JS兩個Point渠驼,screenLocation和pageLoacation,以及手勢的wx_identifier鉴腻。這部分和輕掃手勢基本一樣,不多贅述百揭。
(四)拖動事件
拖動事件在Weex里面包含5個事件爽哎。分別對應(yīng)著拖動的5種狀態(tài):拖動開始,拖動中器一,拖動結(jié)束课锌,水平拖動,豎直拖動祈秕。
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)
為了區(qū)分上面5種狀態(tài)渺贤,Weex還對每個狀態(tài)增加了一個BOOL變量來判斷當(dāng)前的狀態(tài)。分別如下:
BOOL _listenPanStart;
BOOL _listenPanMove;
BOOL _listenPanEnd;
BOOL _listenHorizontalPan;
BOOL _listenVerticalPan;
通過宏增加的5個事件请毛,實質(zhì)都是執(zhí)行了addPanGesture方法志鞍,只不過每個狀態(tài)的事件都會跟對應(yīng)的BOOL變量。
- (void)addPanStartEvent
{
// 拖動開始
_listenPanStart = YES;
[self addPanGesture];
}
- (void)addPanMoveEvent
{
// 拖動中
_listenPanMove = YES;
[self addPanGesture];
}
- (void)addPanEndEvent
{
// 拖動結(jié)束
_listenPanEnd = YES;
[self addPanGesture];
}
- (void)addHorizontalPanEvent
{
// 水平拖動
_listenHorizontalPan = YES;
[self addPanGesture];
}
- (void)addVerticalPanEvent
{
// 豎直拖動
_listenVerticalPan = YES;
[self addPanGesture];
}
最終都是調(diào)用addPanGesture方法:
- (void)addPanGesture
{
if (!_panGesture) {
_panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPan:)];
_panGesture.delegate = self;
[self.view addGestureRecognizer:_panGesture];
}
}
給當(dāng)前的視圖增加一個拖動手勢方仿,觸發(fā)的方法是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此洲,這里不再贅述。
根據(jù)_listenPanStart委粉,_listenPanEnd呜师,_listenPanMove判斷當(dāng)前的狀態(tài),并生成與之對應(yīng)的eventName和state字符串贾节。
根據(jù)_panGesture在當(dāng)前視圖上拖動形成的有方向的向量汁汗,進(jìn)行判斷當(dāng)前拖動的方向趟紊。
(五)通用觸摸事件
最后就是通用的觸摸事件。
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種狀態(tài)送爸。
WXTouchGestureRecognizer會弱引用當(dāng)前的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個事件墨吓,實質(zhì)都是改變每個狀態(tài)的事件都會跟對應(yīng)的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;
}
當(dāng)用戶開始觸摸屏幕纹磺,在屏幕上移動帖烘,手指從屏幕上結(jié)束觸摸,取消觸摸橄杨,分別都會觸發(fā)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個事件里面實質(zhì)都是在調(diào)用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采转。原理一致聪廉。
至于坐標(biāo)如何傳遞給JS見第二章。
2. Appear 事件
如果一個位于某個可滾動區(qū)域內(nèi)的組件被綁定了 appear 事件故慈,那么當(dāng)這個組件的狀態(tài)變?yōu)樵谄聊簧峡梢姇r板熊,該事件將被觸發(fā)。
所以綁定了Appear 事件的都是可以滾動的視圖察绷。
WX_ADD_EVENT(appear, addAppearEvent)
通過上述的宏給可以滾動的視圖增加Appear 事件干签。也就是當(dāng)前視圖執(zhí)行addAppearEvent方法。
- (void)addAppearEvent
{
_appearEvent = YES;
[self.ancestorScroller addScrollToListener:self];
}
在Weex的每個組件里面都有2個BOOL記錄著當(dāng)前_appearEvent和_disappearEvent的狀態(tài)克婶。
BOOL _appearEvent;
BOOL _disappearEvent;
當(dāng)增加對應(yīng)的事件的時候筒严,就會把對應(yīng)的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 事件都必須要求是滾動視圖情萤,所以這里會遍歷當(dāng)前視圖的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筋岛,數(shù)組里面裝的都是被監(jiān)聽的對象娶视。添加進(jìn)這個數(shù)組會先判斷當(dāng)前是否有相同的WXScrollToTarget,避免重復(fù)添加,如果沒有重復(fù)的就新建一個WXScrollToTarget肪获,再添加進(jìn)listenerArray中寝凌。
@interface WXScrollToTarget : NSObject
@property (nonatomic, weak) WXComponent *target;
@property (nonatomic, assign) BOOL hasAppear;
@end
WXScrollToTarget是一個普通的對象,里面弱引用了當(dāng)前需要監(jiān)聽的WXComponent,以及一個BOOL變量記錄當(dāng)前是否Appear了。
當(dāng)滾動視圖滾動的時候牺勾,就會觸發(fā)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]就是觸發(fā)了判斷是否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數(shù)組里面的每個WXScrollToTarget對象都調(diào)用scrollToTarget:scrollRect:方法。根據(jù)當(dāng)前滾動的情況傳入一個CGRect致开,這個CGRect就是當(dāng)前滾動到那個矩形區(qū)域的坐標(biāo)信息以及寬和高峰锁。
- (void)scrollToTarget:(WXScrollToTarget *)target scrollRect:(CGRect)rect
{
WXComponent *component = target.target;
if (![component isViewLoaded]) {
return;
}
// 計算出當(dāng)前的可見區(qū)域的頂部坐標(biāo)
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;
}
// 計算出當(dāng)前的可見區(qū)域的底部坐標(biāo)
CGFloat cbottom = ctop + CGRectGetHeight(component.calculatedFrame);
// 計算出當(dāng)前的可見區(qū)域的左邊界坐標(biāo)
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;
}
// 計算出當(dāng)前的可見區(qū)域的右邊界坐標(biāo)
CGFloat cright = cleft + CGRectGetWidth(component.calculatedFrame);
// 獲取傳入的滾動的區(qū)域
CGFloat vtop = CGRectGetMinY(rect), vbottom = CGRectGetMaxY(rect), vleft = CGRectGetMinX(rect), vright = CGRectGetMaxX(rect);
// 判斷當(dāng)前可見區(qū)域是否包含在傳入的滾動區(qū)域內(nèi),如果在双戳,并且監(jiān)聽了appear事件虹蒋,就觸發(fā)appear事件,否則如果監(jiān)聽了disappear事件就觸發(fā)disappear事件
if(cbottom > vtop && ctop <= vbottom && cleft <= vright && cright > vleft){
if(!target.hasAppear && component){
target.hasAppear = YES;
// 如果當(dāng)前監(jiān)聽了appear飒货,就觸發(fā)appear事件
if (component->_appearEvent) {
[component fireEvent:@"appear" params:_direction ? @{@"direction":_direction} : nil];
}
}
} else {
if(target.hasAppear && component){
target.hasAppear = NO;
// 如果當(dāng)前監(jiān)聽了disappear魄衅,就觸發(fā)disappear事件
if(component->_disappearEvent){
[component fireEvent:@"disappear" params:_direction ? @{@"direction":_direction} : nil];
}
}
}
}
scrollToTarget:scrollRect:方法的核心就是拿當(dāng)前可視區(qū)域和傳入的滾動區(qū)域進(jìn)行對比,如果在該區(qū)域內(nèi)膏斤,且監(jiān)聽了appear事件徐绑,就會觸發(fā)appear事件,如果不在該區(qū)域內(nèi)莫辨,且監(jiān)聽了disappear事件,就會觸發(fā)disappear事件毅访。
3. Disappear 事件
如果一個位于某個可滾動區(qū)域內(nèi)的組件被綁定了 disappear 事件沮榜,那么當(dāng)這個組件被滑出屏幕變?yōu)椴豢梢姞顟B(tài)時,該事件將被觸發(fā)喻粹。
同理蟆融,綁定了Disappear 事件的都是可以滾動的視圖。
WX_ADD_EVENT(disappear, addDisappearEvent)
通過上述的宏給可以滾動的視圖增加Disappear 事件守呜。也就是當(dāng)前視圖執(zhí)行addDisappearEvent方法型酥。
- (void)addDisappearEvent
{
_disappearEvent = YES;
[self.ancestorScroller addScrollToListener:self];
}
接下去的和Appear 事件的原理就一模一樣了。
4. Page 事件
暫時Weex只支持 iOS 和 Android查乒,H5 暫不支持弥喉。
Weex 通過 viewappear 和 viewdisappear 事件提供了簡單的頁面狀態(tài)管理能力。
viewappear 事件會在頁面就要顯示或配置的任何頁面動畫被執(zhí)行前觸發(fā)玛迄,例如由境,當(dāng)調(diào)用 navigator 模塊的 push 方法時,該事件將會在打開新頁面時被觸發(fā)。viewdisappear 事件會在頁面就要關(guān)閉時被觸發(fā)虏杰。
與組件Component的 appear 和 disappear 事件不同的是讥蟆,viewappear 和 viewdisappear 事件關(guān)注的是整個頁面的狀態(tài),所以它們必須綁定到頁面的根元素上纺阔。
特殊情況下瘸彤,這兩個事件也能被綁定到非根元素的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里面质况,有這樣一個更新當(dāng)前Instance狀態(tài)的方法,這個方法里面就會觸發(fā) viewappear 和 viewdisappear 事件婆翔。
其中WX_SDK_ROOT_REF就是_root
#define WX_SDK_ROOT_REF @"_root"
上述更新狀態(tài)的方法同樣出現(xiàn)在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
在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可以是字符串或者字典。
這兩個閉包的區(qū)別在于:
- WXModuleCallback用于Module組件瘟则,為了節(jié)約內(nèi)存黎炉,該回調(diào)只能回調(diào)通知JS一次,之后會被釋放醋拧,多用于一次結(jié)果慷嗜。
- WXModuleKeepAliveCallback同樣是用于Module組件,但是該回調(diào)可以設(shè)置是否為多次回調(diào)類型丹壕,如果設(shè)置了keepAlive庆械,那么可以進(jìn)行持續(xù)監(jiān)聽變化,多次回調(diào)菌赖,并返回給 JS缭乘。
在Weex中使用WXModuleCallback回調(diào),很多情況是把狀態(tài)回調(diào)給JS琉用,比如成功或者失敗的狀態(tài)堕绩,還有一些出錯的信息回調(diào)給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];
}
在調(diào)用setItem:value:callback:方法里面邑时,如果setKey-value的時候失敗了奴紧,會把錯誤信息通過WXModuleCallback回調(diào)給JS。
當(dāng)然刁愿,如果調(diào)用存儲模塊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:方法調(diào)用成功绰寞,會把成功的狀態(tài)和數(shù)據(jù)通過WXModuleCallback回調(diào)給JS。
在Weex中使用了WXModuleKeepAliveCallback的模塊總共只有以下4個:
WXDomModule,WXStreamModule滤钱,WXWebSocketModule觉壶,WXGlobalEventModule
在WXDomModule模塊中,JS調(diào)用獲取Component組件的位置信息和寬高信息的時候件缸,需要把這些坐標(biāo)和尺寸信息回調(diào)給JS铜靶,不過這里雖然用到了WXModuleKeepAliveCallback,但是keepAlive是false他炊,并沒有用到多次回調(diào)的功能争剿。
在WXStreamModule模塊中,由于這是一個傳輸流的模塊痊末,所以肯定需要用到WXModuleKeepAliveCallback蚕苇,需要持續(xù)不斷的監(jiān)聽數(shù)據(jù)的變化,并把進(jìn)度回調(diào)給JS凿叠,這里用到了keepAlive涩笤。WXStreamModule模塊中也會用到WXModuleCallback,WXModuleCallback會即時把各個狀態(tài)回調(diào)給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回調(diào)蹬碧,這4個callback分別是把error錯誤信息,message收到的數(shù)據(jù)炒刁,open打開鏈接的狀態(tài)恩沽,close關(guān)閉鏈接的狀態(tài),持續(xù)的回調(diào)給JS翔始。
在WXGlobalEventModule模塊中罗心,有一個fireGlobalEvent:方法。
- (void)fireGlobalEvent:(NSNotification *)notification
{
NSDictionary * userInfo = notification.userInfo;
NSString * userWeexInstanceId = userInfo[@"weexInstance"];
WXSDKInstance * userWeexInstance = [WXSDKManager instanceForID:userWeexInstanceId];
// 防止userInstanceId存在城瞎,但是instance實際已經(jīng)被銷毀了
if (!userWeexInstanceId || userWeexInstance == weexInstance) {
for (WXModuleKeepAliveCallback callback in _eventCallback[notification.name]) {
callback(userInfo[@"param"], true);
}
}
}
開發(fā)者可以通過WXGlobalEventModule進(jìn)行全局的通知协屡,在userInfo里面可以夾帶weexInstance的參數(shù)。native是不需要關(guān)心userWeexInstanceId全谤,這個參數(shù)是給JS用的。
Native開發(fā)者只需要在用到了WXGlobalEventModule的模塊里加上事件的監(jiān)聽者爷贫,然后發(fā)送全局通知即可认然。userInfo[@"param"]會被回調(diào)給JS。
(2)fireEvent:params:domChanges:
在開頭我們介紹的Weex事件的4種類型漫萄,通用事件卷员,Appear 事件,Disappear 事件腾务,Page 事件毕骡,全部都是通過fireEvent:params:domChanges:這種方式,Native觸發(fā)事件之后,Native把參數(shù)傳遞給JS的未巫。
在WXComponent里面定義了2個可以給JS發(fā)送消息的方法:
/**
* @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;
這兩個方法的區(qū)別就在于最后一個domChanges的參數(shù)窿撬,有這個參數(shù)的方法主要多用于Weex的Native和JS的雙向數(shù)據(jù)綁定。
- (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];
}
上述就是兩個方法的具體實現(xiàn)叙凡∨椋可以看到fireEvent:params:方法就是調(diào)用了fireEvent:params:domChanges:方法,只不過最后的domChanges參數(shù)傳了nil握爷。
在fireEvent:params:domChanges:方法中會對params字典做了一次加工跛璧,加上了timestamp的鍵值。最終還是會調(diào)用WXBridgeManager 里面的fireEvent:ref: type:params:domChanges:方法新啼。
在WXBridgeManager中具體實現(xiàn)了上述的兩個方法追城。
- (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];
}
入?yún)ef, type, params, domChanges封裝到最終的args參數(shù)數(shù)組里面,最后會封裝出WXCallJSMethod方法燥撞,通過WXBridgeManager的callJsMethod調(diào)用到JS的fireEvent方法座柱。
這里可以舉個例子:
假設(shè)一個場景,用戶點(diǎn)擊了一張圖片叨吮,于是就會改變label上的一段文字辆布。
首先圖片是imageComponent,用戶點(diǎn)擊會觸發(fā)該Component的onclick:方法
組件里面會調(diào)用fireEvent:params:方法:
[self fireEvent:@"click" params:@{@"position":position}];
最終通過fireEvent:params:domChanges:方法茶鉴,發(fā)送給JS的參數(shù)字典大概如下:
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方法調(diào)用以后锋玲,處理完,知道label需要更新涵叮,于是又會開始call Native惭蹂,調(diào)用Native的方法。調(diào)用Native的callNative方法割粮,發(fā)過來的參數(shù)如下:
(
{
args = (
4,
{
value = "\U56fe\U7247\U88ab\U70b9\U51fb";
}
);
method = updateAttrs;
module = dom;
}
)
最終會調(diào)用Dom的updateAttrs方法盾碗,會去更新id為4的value,id為4對應(yīng)的就是label舀瓢,更新它的值就是刷新label廷雅。
接著JSFramework還會繼續(xù)調(diào)用Native的callNative方法,發(fā)過來的參數(shù)如下:
(
{
args = (
);
method = updateFinish;
module = dom;
}
)
調(diào)用Dom的updateFinish方法京髓,即頁面刷新完畢航缀。
最后
至此,Weex從View的創(chuàng)建堰怨,到渲染芥玉,產(chǎn)生事件回調(diào)JSFramework,這一系列的流程源碼都解析完成了备图。
中間涉及到了3個子線程灿巧,mainThread赶袄,com.taobao.weex.component,com.taobao.weex.bridge抠藕,分別是UI主線程饿肺,DOM線程,JSbridge線程幢痘。
Native端目前還差神秘的JSFramework的源碼解析唬格。請大家多多指點(diǎn)。
Weex 源碼解析系列文章:
Weex 是如何在 iOS 客戶端上跑起來的
由 FlexBox 算法強(qiáng)力驅(qū)動的 Weex 布局引擎
Weex 事件傳遞的那些事兒
Weex 中別具匠心的 JS Framework
iOS 開發(fā)者的 Weex 偽最佳實踐指北