美文网首页免费
换肤框架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