iOS换肤功能的简单处理框架
2019独角兽企业重金招聘Python工程师标准>>>
换肤功能是在APP开发过程中遇到的比较多的场景,为了提供更好的用户体验,许多APP会为用户提供切换主题的功能。主题颜色管理涉及到的的步骤有
- 颜色配置
- 使用颜色
- UI元素动态变更的能力
- 动态修改配置
- 主题包管理
- 如何实施
- 优化
效果如下:

DEMO代码:https://gitee.com/dhar/iosdemos/tree/master/YTThemeManagerDemo
颜色配置
因为涉及到多种配置,所以以代码的方式定义颜色实践和维护的难度是比较高的,一种合适的方案是--颜色的配置是通过配置文件的形式进行导入的。配置文件会经过转换步骤,最终形成代码层级的配置,以全局的方式提供给各个模块使用,这里会涉及到一个颜色管理者的概念,一般地这回事一个单例对象,提供全局访问的接口。同一个APP中在不同的模块中保存不同的主题颜色配置,在不同的层级中也可以存在不同的主题颜色配置,因为涉及到层级间的配置差异,所以颜色的配置需要引入一个等级的概念,一般地较高层级颜色的配置等级是高于较低层级的,存在相同的配置较高层级的配置会覆盖较低层级的配置。
我们采用的颜色配置的文件形如下面所示,为什么是在一个json文件的color
key下面呢,是为了考虑到未来的扩展性,如果不同的主题会涉及到一些尺寸值的差异化,我们可以添加dimensions
key进行扩展配置。
{"color": {"Black_A":"323232","Black_AT":"323232","Black_B":"888888","Black_BT":"888888","White_A":"ffffff","White_AT":"ffffff","White_AN":"ffffff","Red_A":"ff87a0","Red_AT":"ff87a0","Red_B":"ff5073","Red_BT":"ff5073","Colour_A":"377ce4","Colour_B":"6aaafa","Colour_C":"ff8c55","Colour_D":"ffa200","Colour_E":"c4a27a",}
}
有了以上的配置,颜色配置的工作主要就是解析该配置文件,把配置保存在一个单例对象中即可,这部分主要的步骤如下:
- 配置文件类表根据等级排序
- 获取每个配置文件中的配置,进行保存
- 通知外部主题颜色配置发生改变
对应的代码如下,这里有个需要注意的地方是,加载配置文件的时候使用了文件读写锁进行读写的锁定操作,防止读脏数据的发生,直到配置文件加载完成,释放读写锁,这时读进程可以继续。
- (void)loadConfigWithFileName:(NSString *)fileName level:(NSInteger)level {if (fileName.length == 0) {return;}pthread_rwlock_wrlock(&_rwlock);__block BOOL finded = NO;[self.configFileQueue enumerateObjectsUsingBlock:^(YTThemeConfigFile *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {if ([obj.fileName isEqualToString:fileName]) {finded = YES;*stop = YES;}}];if (!finded) {// 新增配置文件YTThemeConfigFile *file = [[YTThemeConfigFile alloc] init];file.fileName = fileName;file.level = level;[self.configFileQueue addObject:file];// 优先级排序[self.configFileQueue sortUsingComparator:^NSComparisonResult(YTThemeConfigFile *_Nonnull obj1, YTThemeConfigFile *_Nonnull obj2) {if (obj1.level > obj2.level) {return NSOrderedDescending;}return NSOrderedAscending;}];[self setupConfigFilesContainDefault:YES];}pthread_rwlock_unlock(&_rwlock);
}- (void)setupConfigFilesContainDefault:(BOOL)containDefault {NSMutableDictionary *defaultColorDict = nil, *currentColorDict = nil;// 加载默认配置if (containDefault) {defaultColorDict = [NSMutableDictionary dictionary];[self loadConfigDataWithColorMap:defaultColorDict valueMap:nil isDefault:YES];self.defaultColorMap = defaultColorDict;}// 加载主题配置if (_themePath.length > 0) {currentColorDict = [NSMutableDictionary dictionary];[self loadConfigDataWithColorMap:currentColorDict valueMap:nil isDefault:NO];self.currentColorMap = currentColorDict;}// 发送主体颜色变更通知[self notifyThemeDidChange];
}- (void)notifyThemeDidChange {NSArray *allActionObjects = self.actionMap.objectEnumerator.allObjects;for (YTThemeAction *action in allActionObjects) {[action notifyThemeDidChange];}
}- (void)loadConfigDataWithColorMap:(NSMutableDictionary *)colorMap valueMap:(NSMutableDictionary *)valueMap isDefault:(BOOL)isDefault {// 每一次新增一个配置文件,所有配置文件都得重新计算一次,这里有很多重复多余的工作[self.configFileQueue enumerateObjectsUsingBlock:^(YTThemeConfigFile *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {NSDictionary *dict = nil;if (isDefault) {dict = obj.defaultDict;} else {dict = obj.currentDict;}if (dict.count > 0) {[self loadThemeColorTo:colorMap from:dict]; // 将所有配置表中的color字段的数据都放到colorMap中}}];
}- (void)loadThemeColorTo:(NSMutableDictionary *)dictionary from:(NSDictionary *)from {NSDictionary<NSString *, NSString *> *colors = from[@"color"];[colors enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) {// 十六进制字符串转为UIColorUIColor *color = [UIColor yt_nullcolorWithHexString:obj];if (color) {[dictionary setObject:color forKey:key];} else {[dictionary setObject:obj forKey:key];}}];
}
管理者处理处理配置之外,还需要暴露外部接口给客户端使用,以用于获取不同主题下对应的颜色色值、图片资源、尺寸信息等和主题相关的信息。比如我们会提供一个colorForKey
方法获取不同主题下的同一个key对应的颜色色值,获取色值的大致步骤如下:
- 从当前的主题配置中获取
- 从默认的主题配置中获取
- 从预留的主题配置中获取
- 如果重定向的配置,递归处理
- 以上步骤都完成还未找到返回默认黑色
这里使用了读写锁的写锁,如果同时有写操作获取了该锁,读取进程会阻塞直到写操作的完成释放锁。
/**获取颜色值*/
- (UIColor *)colorForKey:(NSString *)key {pthread_rwlock_rdlock(&_rwlock);UIColor *color = [self colorForKey:key isReserveKey:NO redirectCount:0];pthread_rwlock_unlock(&_rwlock);return color;
}- (UIColor *)colorForKey:(NSString *)key isReserveKey:(BOOL)isReserveKey redirectCount:(NSInteger)redirectCount {if (key == nil) {return nil;}///正常获取色值id colorObj = [_currentColorMap objectForKey:key];if (colorObj == nil) {colorObj = [_defaultColorMap objectForKey:key];}if (isReserveKey && colorObj == nil) {return nil;}///看看是否有替补keyif (colorObj == nil) {NSString *reserveKey = [_reserveKeyMap objectForKey:key];if (reserveKey) {colorObj = [self colorForKey:reserveKey isReserveKey:YES redirectCount:redirectCount];}}///查看当前key 能否转成 colorif (colorObj == nil) {colorObj = [UIColor yt_colorWithHexString:key];}if ([colorObj isKindOfClass:[UIColor class]]) {///如果是 重定向 或者 替补 key 的color 要设置到 当前 colorDict 里面// 重定向的配置形如:"Red_A":"Red_B",if (redirectCount > 0 || isReserveKey) {[_currentColorMap ?: _defaultColorMap setObject:colorObj forKey:key];}return colorObj;} else {if (redirectCount < 3) { // 重定向递归return [self colorForKey:colorObj isReserveKey:NO redirectCount:redirectCount + 1];} else {return [UIColor blackColor];}}
}
使用颜色
颜色的使用也是经由管理者的,为了方便,定义一个颜色宏提供给客户端使用
#define YTThemeColor(key) ([[YTThemeManager sharedInstance] colorForKey:key])
客户端使用的代码如下:
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 20, 200, 40)];
label.text = @"Text";
label.textColor = YTThemeColor(kCK_Red_A);
label.backgroundColor = YTThemeColor(kCK_Black_H);
[self.view addSubview:label];
另外,因为颜色配置的key为字符串类型,直接使用字符串常量并不是个好办法,所以把对应的字符串转换为宏定义是一个相对好的办法。第一个是方便使用,可以使用代码提示;第二个是不容易出错,特别是长的字符串;第三个也会一定程度上的提高效率。
YTColorDefine
类的宏定义
// .h 中的声明
///Black
FOUNDATION_EXTERN NSString *kCK_Black_A;
FOUNDATION_EXTERN NSString *kCK_Black_AT;
FOUNDATION_EXTERN NSString *kCK_Black_B;
FOUNDATION_EXTERN NSString *kCK_Black_BT;// .m 中的定义
NSString *kCK_Black_A = @"Black_A";
NSString *kCK_Black_AT = @"Black_AT";
NSString *kCK_Black_B = @"Black_B";
NSString *kCK_Black_BT = @"Black_BT";
主题包管理
在实际的落地项目中,主题包管理涉及到的事项包括主题包下载和解压和动态加载主题包等内容,最后的一步是更换主题配置文件所在的配置路径,为了演示的方便,我们会把不同主题的资源放置在bundle中某一个特定的文件夹下,通过切换管理者中的主题路径配置来达到切换主题的效果,和动态下载更换主题的步骤是一样的。
管理者提供一个设置主题配置的配置路径的方法,在该方法中改变配置路径的同时,重新加载配置即可,代码如下
/**设置主题文件的路径@param themePath 文件的路径*/
- (void)setupThemePath:(NSString *)themePath {pthread_rwlock_wrlock(&_rwlock);_themePath = [themePath copy];self.currentColorMap = nil;if ([_themePath.lowercaseString isEqualToString:[[NSBundle mainBundle] resourcePath].lowercaseString]) {_themePath = nil;}self.currentThemePath = _themePath;for (int i = 0; i < self.configFileQueue.count; i++) {YTThemeConfigFile *obj = [self.configFileQueue objectAtIndex:i];[obj resetCurrentDict];}[self setupConfigFilesContainDefault:NO];pthread_rwlock_unlock(&_rwlock);
}
如何实施
以上的流程涉及到的只是iOS平台下的一个技术解决方案,真实的实践过程中会涉及到安卓平台、Web页面、UI出图的标注,这些是要进行统一处理的,才能在各个端上有一致的体验。第一步就是制定合理的颜色规范,把规范同步给各个端的利益相关人员;第二部是UI出图颜色是规范的颜色定义值,而不是比如#ffffff这样的颜色,需要是比如White_A这样规范的颜色定义值,这样客户端处理使用的就是White_A这个值,不用管在不同主题下不同的颜色表现形式。
优化
loadConfigDataWithColorMap方法调用的优化
如果模块很多,每个模块都会调用loadConfigWithFileName
加载配置文件,那么loadConfigDataWithColorMap方法处理文件的时间复杂度是O(N*N),会重复处理很多多余的工作,理想的做法是底层保存一份公有的颜色配置,然后在APP层加载一份定制化的配置,在模块中不用再加载主题配置文件,这样会提高效率。
参考资料
读写锁pthread_rwlock_t的使用
转载于:https://my.oschina.net/FEEDFACF/blog/3008287
iOS换肤功能的简单处理框架相关推荐
- 为微信小程序增加换肤功能
起源 之前,我做了一个展示类的微信小程序,本来都快要完结的了,可是突然,我才听说还要给小程序增加一个换肤功能,这个换肤功能可不是简单的写两套不同的样式表就行了,因为他要可以在后台动态替换背景,底图,文 ...
- Android换肤功能实现与换肤框架QSkinLoader使用方式介绍
框架地址:https://github.com/qqliu10u/QSkinLoader 效果图 https://github.com/qqliu10u/QSkinLoader/raw/master/ ...
- Element UI主题换肤功能(基于vue-element-admin框架)
环境信息: 日期:2022-08-05 node版本:v14.15.4 "sass": "1.26.8", "sass-loader": & ...
- 仿造百度换肤功能的实现
换肤功能的应用很广,不管是搜索界面还是普通的管理界面等等,都可以进行设计并且应用换肤功能,起到更好的用户体验. 今天仿造百度的换肤功能,实现了基本的换肤功能,接下来将会为大家介绍如何实现.在设计界面的 ...
- 换肤功能原理及自定义组件化UI样式初步尝试
只从UI工作开始向前端工作,我一直计划着开发一套属于自己的UI框架,网站通过拖拽点击,或输入布局代码,后台自动生成一套静态页面,从此前端工作仅需要补充各种排版即可,静态页面通过代码生成. 可能我上面的 ...
- android 换肤功能
市面上的app大部分都有换肤功能,今天我来给大家介绍一下实现换肤功能的三种实现方案. 一. 使用系统默认的主题Theme.AppCompat.DayNight app主题必须是Theme.AppCom ...
- 在Ajax程序中实现无刷新换肤功能(asp.net2.0)
写了一年多的WEB程序,觉得程序中换肤的功能是非常吸引人眼球的技术.特别是在子父级的WEB平台与论坛上应用广泛,可以突出不同人的风格与个性. 从文章的标题上看是Ajax的无刷新换肤,只是本 ...
- Android 换肤功能的实现(Apk插件方式)
一.概述 由于Android 没有提供一套统一的换肤机制,我猜可能是因为国外更注重功能和体验的原因 所以国内如果要做一个漂亮的换肤方案,需要自己去实现. 目前换肤的方法大概有三种方案: (1)把皮肤资 ...
- 微信小程序 实现换肤功能
参考链接: (1)微信小程序实现换肤功能 https://www.jb51.net/article/136445.htm (2)微信小程序实现换肤功能 https://blog.csdn.net/qq ...
- Qt - 换肤功能实现
文章目录 前言 Qt内置风格 QPalette QSS QSS样式 一般样式 选择器 子控件 伪状态 属性 使用 分离QSS 推荐工具 Qsseditor QssStylesheetEditor QS ...
最新文章
- Oracle集合操作函数:Union、Union All、Intersect、Minus
- LPC43xx SGPIO Pattern Match Mode
- Latent dirichlet allocation note -- Prepare
- 短视频自研还是选择第三方?技术选型前必看的自检清单
- 【博弈】取石子游戏(P2599)
- Barebox for Tiny6410(LCD驱动移植)
- 常用的密码破解方法大汇总 zz
- bzoj 1606 [Usaco2008 Dec]Hay For Sale 购买干草(01背包)
- python调用百度地图画轨迹图_利用python和百度地图API实现数据地图标注的方法
- ai面试的优缺点_AI面试是什么?有哪些特点呢?
- android studio logcat 字体,android studio - 修改logcat颜色字体
- echarts瀑布图_一种基于阶梯瀑布图的数据计算方法与流程
- 2048C语言源码linux
- JZ3 从头到尾打印链表
- 注会 第三章 存货
- 功率W与dBm以及SINR/RSRP/RSRQ含义
- 2021年湖南卫生副高考试成绩查询,2021年卫生职业考试成绩查询具体流程湖南
- jenkins依赖的android sdk下载安装
- c语言指针flash,STM32F103RCT6之FLASH读写操作
- 视频号的直播还有视频怎么下载呀?