React Native 03:换肤支持

因为原生这边有个换肤功能,用户下载对应的皮肤包,app会重新渲染一些页面元素色值,以及替换相关图片。某一天产品跑来说,RN的页面也要支持换肤哦。虽然我默默的从抽屉里拿出来一把caidao。但还是要去想办法把它搞掉。


思路如下:

1.先去看看原生那边换肤是怎么玩的。
  • 点开app下载一个皮肤包。会从服务器请求一个zip压缩包。
  • 解压缩打开,一份色值表配置json文件,若干替换图片。

看代码。大致实现是换肤库提供了很多的UIKit组件的分类,提供了相关设置色值,图片的方法。并且会去注册监听换肤通知。一般我们写业务代码的时候,都会使用那些分类所提供的支持换肤的方法。并且部分页面需要主动监听换肤通知的从而实现主动渲染。当触发换肤的时候,页面重新渲染,就会从对应的皮肤包里获取对应的图片,以及色值。从而实现的换肤。

2.RN这边既然要支持换肤,不就是JS中设置图片,色值的地方改成通过桥接从原生那边获取对应的色值和图片吗?
  • 色值
    首先原生写好了一个桥接方法,传入不同的key去色值表中查找对应的16进制色值,返回给rn。rn页面在render()函数之前,调用该桥接获取色值,并且通过state保存该色值。之后在render()函数渲染的时候设置到对应的节点上就行了。好吧。是可以。后来又发现了个问题。RN中原生和JS桥接是异步执行,那么就不能保证在render()函数执行之前获取到的是真正的色值。很尴尬。这种方式好像行不通。换个思路。
  • 图片
    其实RN加载图片,是通过RCTImageLoader这个类。通过断点发现主要调用下面的那个方法。
-(RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest *)request
                                                             size:(CGSize)size
                                                            scale:(CGFloat)scale
                                                       resizeMode:(RCTResizeMode)resizeMode
                                                    progressBlock:(RCTImageLoaderProgressBlock)progressHandler
                                                 partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadHandler
                                                  completionBlock:(void (^)(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate))completionBlock {
}

主要的一个参数就是request。如果是网络图片就是正常的http:// 协议的链接。如果是本地图片,不管是项目image.xcassets里的图片还是mainBundle里的图片,又或者是沙盒里的图片,都会是一个file://协议的图片本地地址。好吧。替换下图片路径试试?结果可行。那么RN图片换肤就可以通过修改源码来实现。

3.图片搞定了,还有色值呢?既然图片可以通过修改源码的形式搞定,那么色值能不能也通过修改源码的方式去搞?找到原生这边生成UIColor的方法?之前RN那部分不仅支持16进制的色值,而且还支持字符串类型的色值,例如设置'red',RN最终会设置成真正的色值。RN是不是通过这个字符串key,去它自己的色值表去查找对应色值的呢?那么就要找到将‘red’解析成真正色值的方法。
  • 原生部分
    找了半天,发现了RCTConvert中有个方法是用来转换颜色的。断点发现RN中的设置颜色最终都会来到该方法里转换成UIColor的对象。
  + (UIColor *)UIColor:(id)json
{
  if (!json) {
    return nil;
  }
  if ([json isKindOfClass:[NSString class]]) {
      //去皮肤包查找色值
      if([json isEqualToString:@"ck_black"]) {
          return [UIColor whiteColor];
      }
  }
  if ([json isKindOfClass:[NSArray class]]) {
    NSArray *components = [self NSNumberArray:json];
    CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0;
    return [UIColor colorWithRed:[self CGFloat:components[0]]
                           green:[self CGFloat:components[1]]
                            blue:[self CGFloat:components[2]]
                           alpha:alpha];
  } else if ([json isKindOfClass:[NSNumber class]]) {
    NSUInteger argb = [self NSUInteger:json];
    CGFloat a = ((argb >> 24) & 0xFF) / 255.0;
    CGFloat r = ((argb >> 16) & 0xFF) / 255.0;
    CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
    CGFloat b = (argb & 0xFF) / 255.0;
    return [UIColor colorWithRed:r green:g blue:b alpha:a];
  } else {
    RCTLogConvertError(json, @"a UIColor. Did you forget to call processColor() on the JS side?");
    return nil;
  }
}

好吧。看起来该方法里只支持从RN那边传递过来的NSArray类型和NSNumber类型的。也不支持NSString类型的啊。所以RN中通过'red'这种字符串设置颜色的解析并不是在原生这部分。所以在原生部分支持字符串类型。通过该字符串key去对应皮肤包的色值表中去查找真正的色值。

  • RN部分
    发现node_modules/react-native/Libraries/StyleSheet/processColor.js该文件中processColor()函数是用来解析色值的。
  var Platform = require('Platform');
  var normalizeColor = require('normalizeColor');
/* eslint no-bitwise: 0 */
function processColor(color) {
  if (color === undefined || color === null) {
    return color;
  }

  var int32Color = normalizeColor(color);
  if (int32Color === null) {
    return undefined;
  }

  if(typeof int32Color === 'string') {
    return int32Color;
  }

  // Converts 0xrrggbbaa into 0xaarrggbb
  int32Color = (int32Color << 24 | int32Color >>> 8) >>> 0;

  if (Platform.OS === 'android') {
    // Android use 32 bit *signed* integer to represent the color
    // We utilize the fact that bitwise operations in JS also operates on
    // signed 32 bit integers, so that we can use those to convert from
    // *unsigned* to *signed* 32bit int that way.
    int32Color = int32Color | 0x0;
  }
  return int32Color;
}

在该方法中,RN会去查找类似'red'这样的色值,并且返回为一个32位int类型。如果没找到对应色值,它会强转为一个0x0的色值。所以我们在它查找完之后,强转之前,如果没找到,直接将字符串类型的变量返回出去,这样原生也就能接受到一个字符串类型的key了。

最后

最后RN换肤实践成功实现了产品的需求。就是找代码的过程很蓝瘦,尤其是JS的那部分。如果RN版本升级了,动过的源码部分也要同步过去。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容