需求背景
对于一些使用广泛可变性强的组件比如按钮,单一的视觉无法满足其他项目直接调用,为满足应用界面设计的多变性,同时提高开发者效率,我们希望提高组件库的可定制化,因此提供换肤功能以及多种类组件中的样式定制功能,允许其他项目调用组件库时可以自己定义自己项目的主题风格的皮肤,也允许开发者对指定组件进行样式改造。
设计目标
性能
一个方案的落地前提得有性能的保障,不重新初始化视图,避免出现闪屏、卡顿等性能缺陷现象,同时也要保障功能稳定,不能存在部分组件不按预期切换主题想象。
可维护性
组件库需不断迭代完善,应避免过多的条件判断,避免在单个组件上有过多的主题特殊逻辑,主题的设置和组件的实现应解藕,保证后续可维护可扩展。
可配置
可配置分为两部分,一部分为可配置任意全局统一的样式变量,或者某个组件的局部样式;另一部分为强制模式,即指定部分组件不跟随主题变化而变化,保留着本身的一种样式。
易用性
提供快捷接入主题的接口,降低学习成本和时间成本。
粒度细分
组件层面的主题定制、整套组件库的主题定制。开发者可以修改全局样式,比如更换全局中字号的字体大小,也可以局部修改样式,比如按钮组件的边框颜色。
样式提取
暴露出提取当前整套样式的接口,方便开发者提取指定样式做二次操作。比如开发者需要提取当前主题颜色作为视图背景色,可从组件库中获取。
样式可定制内容,包括但不限于:
颜色、文本、按钮、图片
设计方案
主题功能目录结构
[图片上传失败...(image-b8a671-1655458474131)]
wsf_initializer: 主题初始化文件,由组件库使用方调用;
wsf_theme: 主题导出;
wsf_theme_configurator: 主题配置器,可以配置一套默认主题、获取配置主题、注册主题;
configs文件夹:放组件配置文件;
base文件夹: 基础配置类以及一些给定的配置参数;
生成样式配置文件
配置文件大体分为这三类:全部配置文件、公共配置文件和组件配置文件
全部配置文件:主要是用于集合公共配置和组件配置, 它的构造函数如下:
WsfAllThemeConfig({
WsfCommonConfig? commonConfig,
WsfButtonConfig? buttonConfig,
..
String configId = globalConfigID,
}) : _commonConfig = commonConfig,
_buttonConfig = buttonConfig,
...
_calendarConfig = calendarConfig;
公共配置文件:配置公共样式的对象,它的构造函数如下:
WsfCommonConfig({
Color? brandPrimary,
Color? brandSuccess,
Color? brandWarning,
...
String configId = globalConfigID,
}) : _brandPrimary = brandPrimary,
_brandSuccess = brandSuccess,
_brandWarning = brandWarning,
...
super(configId: configId);
组件配置文件:配置需要配置主题的某一组件的对象,它的构造函数如下:
WsfButtonConfig({
double? btnRadius,
double? btnHeight,
...
String configId = globalConfigID,
}) : _btnRadius = btnRadius,
_btnHeight = btnHeight,
...
super(configId: configId);
主题配置的使用
调用初始化文件,将项目上下午context传入主题注册函数中用于screenutil插件完成自适应功能
WsfInitializer.register(context: context);
WsfInitializer.register(context: context,allThemeConfig); //allThemeConfig为配置的全局主题
单一组件主题配置
//吸底按钮文字加按钮
WsfBottomTextAndButtonPanel(
mainTitle: '主按钮',
themeDataMain: WsfButtonConfig(btnWidth: 120.w),
textChild: const Text("¥200"),
onTapMain: () {
},
),
初始化文件代码
/// Wsf 初始化
class WsfInitializer {
/// 手动注册时,默认注册渠道是 GLOBAL_CONFIG_ID
static register({
required BuildContext context,
WsfAllThemeConfig? allThemeConfig,
String configId = globalConfigID,
}) {
/// 屏幕适配
ScreenUtil.init(
BoxConstraints(
maxWidth: MediaQuery.of(context).size.width,
maxHeight: MediaQuery.of(context).size.height),
designSize: const Size(375, 667),
context: context,
minTextAdapt: true,
orientation: Orientation.portrait,
);
/// 配置主题定制
WsfThemeConfigurator.instance.register(allThemeConfig, configId: configId);
}
}
主题配置器
import 'package:wsf_ui/src/theme/base/wsf_default_config_utils.dart';
import 'configs/wsf_all_config.dart';
const String wsfConfigId = 'WSF_CONFIG_ID';
const String globalConfigID = 'GLOBAL_CONFIG_ID';
class WsfThemeConfigurator {
WsfThemeConfigurator._() {
_checkAndInitWsfConfig();
}
static final WsfThemeConfigurator _instance = WsfThemeConfigurator._();
static WsfThemeConfigurator get instance {
return _instance;
}
Map globalConfig = {};
/// 手动注册时,默认注册渠道是 GLOBAL_CONFIG_ID
void register(
WsfAllThemeConfig? allThemeConfig, {
String configId = globalConfigID,
}) {
// 打平内部字段
if (allThemeConfig != null) {
// 赋值传入配置
globalConfig[configId] = allThemeConfig..initThemeConfig(configId);
}
}
/// 获取合适的配置
/// 1、获取 configId 对应的全局主题配置,
/// 2、若获取的为 null,则使用默认的全局配置。
/// 3、若没有配置 GLOBAL_CONFIG_ID ,则使用 WSF_CONFIG_ID 的配置。
WsfAllThemeConfig getConfig({String configId = globalConfigID}) {
WsfAllThemeConfig? allThemeConfig = globalConfig[configId] ??
globalConfig[globalConfigID] ??
globalConfig[wsfConfigId];
assert(allThemeConfig != null, 'No suitable config found.');
return allThemeConfig!;
}
/// 检查是否有默认配置
bool isWsfConfig() => globalConfig[wsfConfigId] != null;
/// 没有默认配置 配置默认配置
void _checkAndInitWsfConfig() {
if (!isWsfConfig()) {
globalConfig[wsfConfigId] = WsfDefaultConfigUtils.defaultAllConfig;
}
}
}
注册主题可先配置好WsfAllThemeConfig 对象 ,定制好主题, 传入的主题id为:GLOBAL_CONFIG_ID, 默认的id为:WSF_CONFIG_ID
分别为全局主题、默认的组件库主题
主题配置优先级
组件自定义主题 > 外部定义的全局主题 > 组件库默认主题
核心操作
WsfButtonConfig defaultThemeConfig = themeData ?? WsfButtonConfig();
defaultThemeConfig = WsfThemeConfigurator.instance
.getConfig(configId: defaultThemeConfig.configId)
.buttonConfig
.merge(defaultThemeConfig);
themeData为自定义单一主题,WsfButtonConfig() 为默认主题或者全局主题;
WsfButtonConfig() 设计代码如下:
double get btnRadius =>
_btnRadius ?? WsfDefaultConfigUtils.defaultButtonConfig.btnRadius;
组件属性的获取是遵循以下规律,如果外部全局配置了属性则返回该属性,如果没有,则返回组件库默认配置的属性。
如果自定义了单一主题的某一些属性,其他属性采用组件库默认配置的属性,则使用配置对象里面的merge函数,将自定义的属性和默认的组件库属性进行合并,返回新的配置对象;
主题的输出
新建wsf_theme.dart 文件,将组件配置对象export给外部调用
/// theme export
library wsf_theme;
export 'base/wsf_base_config.dart';
export 'base/wsf_default_config_utils.dart';
export 'configs/wsf_all_config.dart';
export 'configs/wsf_button_config.dart';
export 'configs/wsf_calendar_config.dart';
export 'configs/wsf_checkbox_config.dart';
export 'configs/wsf_common_config.dart';
export 'configs/wsf_radio_config.dart';
export 'wsf_initializer.dart';
export 'wsf_theme_configurator.dart';
总结
这个主题设计思想主要采用面向对象的设计方法,有灵活运用抽象类、单例模式、对象属性merge以及运算符??等方式,非常巧妙的分离解藕了主题和组件之间的关系,达到了设计目标,满足了主题定制化以及开发对指定组件样式改造的需求。