美文网首页免费
换肤框架SakuraKit实践及源码解析

换肤框架SakuraKit实践及源码解析

作者: Veer_Pan | 来源:发表于2018-08-14 12:51 被阅读358次

前言

最近在做换肤,但是找了很多方案发现对代码的侵入性都非常大,而且公司项目做了组件化,网上通用的方案虽然也能实现但是代价太大,而且不方便迭代更新。当换肤功能完成大半的时候突发发现一个流弊的换肤框架,SakuraKit我。。。
先附上原文作者的链接:http://www.jianshu.com/p/8930b4496023
作者的 demo本人感觉过于复杂,我这里讲下从简单的使用到源码

使用步骤

  1. 编写json文件Skin_Tennis.json
{
   "PersonalCenter":{
        "skinFlagColor":"ffffff",
        "skinFlagName":"网球主题",
        "backImage":"back_black",
        "backTitleColor":"#999999",
        "headerBackgroundImage":"me_header_tennis",
        "buttonBackgroundImage":"button_background_tennis"
    }
}
  1. 调用
UIImageView *bgImageView = [[UIImageView alloc] init];
bgImageView.contentMode = UIViewContentModeScaleAspectFill;
bgImageView.sakura.image(@"PersonalCenter.headerBackgroundImage");
[self addSubview:bgImageView];

UIButton *button = [[UIButton alloc] init];
button.frame = CGRectMake(0, 0, 100, 44);
button.sakura.backgroundImage(@"PersonalCenter.buttonBackgroundImage", UIControlStateNormal);
button.sakura.titleColor(@"PersonalCenter. backTitleColor", UIControlStateNormal);
  1. 切换皮肤
[TXSakuraManager shiftSakuraWithName:@"Skin_Tennis" type:TXSakuraTypeMainBundle];
  1. 恭喜你完成了一个简单的本地换肤

源码?

我们这里以bgImageView.sakura.image(@"PersonalCenter.headerBackgroundImage");举例,我们点进. sakura看看

TXSakuraCategoryImplementation(UIImageView, TXSakuraImageView)

懵逼,懵逼就对了,作者怕是搞c++出身的,别怕其实就是个宏而已解析出来就是:

@interface UIImageView (TX)
@property (strong, nonatomic) TXSakuraImageView *sakura;
@end

extern void *kTXSakuraKey;

@implementation UIImageView(TX)

@dynamic sakura;
- (UIImageView *)sakura {
    TXSakuraImageView *obj = objc_getAssociatedObject(self, kTXSakuraKey);
    if (!obj) {
        obj = [TXSakuraImageView sakuraWithOwner:self]
        objc_setAssociatedObject(self, kTXSakuraKey, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return obj;
}
@end

一个分类而已,并且给当前UIImageView对象添加了一个sakura参数,并实现懒加载
. sakura知道了,那么来看看.image(@"PersonalCenter.headerBackgroundImage");可以跟参数,那肯定是个block,源码:

- (TXSakuraImageViewBlock)image {
    return (TXSakuraImageViewBlock)[super tx_sakuraImageBlockWithName:NSStringFromSelector(_cmd)];
}

别怕我们一层层来看,注意看注释,有的简单方法我就不展开了直接写结果了

- (TXSakuraBlock)tx_sakuraImageBlockWithName:(NSString *)name {
    
    // 实际name=@"image",path=@"PersonalCenter.headerBackgroundImage"
    return ^TXSakura *(NSString *path){
        return [self send1DMsgObjectWithName:name keyPath:path arg:kTXSakuraArgImage valueBlock:^NSObject *(NSString *keyPath) {
            // 这里是通过keyPath取出对应的image
            return [TXSakuraManager tx_imageWithPath:keyPath];
        }];
    };
}

先来看看send1DMsgObjectWithName方法

- (instancetype)send1DMsgObjectWithName:(NSString *)name
                                keyPath:(NSString *)keyPath
                                    arg:(NSString *)arg
                             valueBlock:(id(^)(NSString *))valueBlock {
    // 这里不过是做了一些拼接生成一个setImage:的sel,并且以sel名为key将keyPaht存到一个全局的字典里面innerSkins1D,最终得到的innerSkins1D是:
    /**
     {
         "setImage:" =     {
            "com.tingxins.sakura.arg.image" = "PersonalCenter.headerBackgroundImage";
         };
     }
     */
    SEL sel = [self prepareForSkin1DWithName:name keyPath:keyPath argKey:arg];
    
    if (!valueBlock) return [TXSakuraTrash sakuraWithOwner:self];
    NSObject *obj = valueBlock(keyPath);
    // 给imageView对象send一个sel的方法也就是调用setImage:,参数为valueBlock返回的值,也就是[TXSakuraManager tx_imageWithPath:keyPath];的返回值
    [self send1DMsgWithSEL:sel objValue:obj];
    return self;
}

看完了设置的方法,那么来看看看切换皮肤的方法(有注释的地方才是关键点)

[TXSakuraManager shiftSakuraWithName:@"Skin_Tennis" type:TXSakuraTypeMainBundle];
+ (BOOL)shiftSakuraWithName:(TXSakuraName *)name type:(TXSakuraType)type {
    if (name &&
        [name isEqualToString:_currentSakuraName]) return NO;
    if (!name) name = kTXSakuraDefault;
    switch (type) {
        case TXSakuraTypeMainBundle:
            _resourcesPath = nil;
            // 这里是通过文件名拿到对应的文件路径
            _configsFilePath = [self tx_getSakuraConfigsFileBundlePathWithName:name];
            break;
        case TXSakuraTypeSandbox:{
            _resourcesPath = [self tx_getSakuraResourceSandboxPathWithName:name];
            _configsFilePath = [self tx_tryGetSakuraConfigsFileSandboxPathWithName:name];
            if (!_configsFilePath.length && _resourcesPath.length) {
                _configsFilePath = [self tx_getSakuraConfigsFileBundlePathWithName:kTXSakuraDefault];
            }
        }
            break;
        default:
            break;
    }
    
    if (_configsFilePath.length) {
        // 这里仅仅把当前选择的皮肤名存到沙盒
        [self saveCurrentSakuraInfosWithName:name type:type];
        // 这里才是关键,通知!大部分换肤框架都免不了通知
        [[NSNotificationCenter defaultCenter] postNotificationName:TXSakuraSkinChangeNotification object:nil];
        return YES;
    }
#ifdef DEBUG
    else {
        NSLog(@"resources not exists!");
    }
    NSLog(@"%@", _configsFilePath);
#endif
    return NO;
}

看到通知是不是明白了大概了,对你猜得没错,之前创建的sakura对象里会接接收这个通知

- (instancetype)initWithOwner:(id)owner {
    if (self = [super init]) {
        _owner = owner;
        _imageRenderingMode = UIImageRenderingModeAlwaysOriginal;
        // TXSakura类里面接收通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateSakuraSkins) name:TXSakuraSkinChangeNotification object:nil];
    }
    return self;
}
- (void)updateSakuraSkins {
    // 一维参数,self.skins1D其实就是返回上面说的全局的innerSkins1D,还记得里面存的什么吗?
    /**
     {
        "setImage:" =     {
            "com.tingxins.sakura.arg.image" = "PersonalCenter.headerBackgroundImage";
        };
     }
     */
    [self updateSakuraWith1DSkins:self.skins1D];
    // 二维参数,不知道干嘛用,好像没用到,为了拓展?
    [self updateSakuraWith2DSkins:self.skins2D];
}

老铁们应该已经猜到接下来该干嘛了吧,updateSakuraWith1DSkins:这里就不展开了,里面就是拿到innerSkins1D这个全局的字典,根据PersonalCenter.headerBackgroundImage这个可以取出创建图片名,然后调用setImage:方法

下载?

待更新

最后感谢看完我BB这么多,希望能带给给需要做换肤的小伙伴有一些启发,demo待我稍作修改稍后附上,有更深入的研究后会更新此文

相关文章

网友评论

    本文标题:换肤框架SakuraKit实践及源码解析

    本文链接:https://www.haomeiwen.com/subject/kzhmbftx.html