第15章:主题与样式
作者:步子哥 (steper@foxmail.com)
15.1 SWT 主题的概念
Dark Mode 的支持
// 检测系统是否是 Dark Mode
boolean isDarkMode = false;
if (Platform.getWS() == Platform.WS_WIN32) {
// Windows 10+ 支持 Dark Mode
// 需要使用 JNA 检测系统主题
// 简化版本:假设用户选择
} else if (Platform.getWS() == Platform.WS_COCOA) {
// Mac 自动支持 Dark Mode
isDarkMode = true;
} else if (Platform.getWS() == Platform.WS_GTK) {
// Linux 需要检测 GTK 主题
isDarkMode = true;
}
if (isDarkMode) {
// 使用 Dark Mode 颜色
shell.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
} else {
// 使用 Light Mode 颜色
shell.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
}
费曼解释:Dark Mode 的含义
Dark Mode = 夜间模式
- 白天(Light Mode):亮色背景,黑色文字
- 晚上(Dark Mode):深色背景,浅色文字
- 保护眼睛,省电
类比:
Dark Mode = 日光灯
- 白天(Light Mode):开大灯(亮色背景)
- 晚上(Dark Mode):开小灯(深色背景)
- 保护眼睛,省电
自定义颜色方案
// 自定义颜色方案
class ColorScheme {
public Color background;
public Color foreground;
public Color selectionBackground;
public Color selectionForeground;
public ColorScheme(Display display, String name) {
if (name.equals("light")) {
background = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
foreground = display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND);
selectionBackground = display.getSystemColor(SWT.COLOR_LIST_SELECTION);
selectionForeground = display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
} else if (name.equals("dark")) {
background = new Color(display, 30, 30, 30);
foreground = new Color(display, 220, 220, 220);
selectionBackground = new Color(display, 60, 120, 180);
selectionForeground = new Color(display, 255, 255, 255);
} else if (name.equals("blue")) {
background = new Color(display, 240, 248, 255);
foreground = new Color(display, 0, 0, 0);
selectionBackground = new Color(display, 0, 120, 215);
selectionForeground = new Color(display, 255, 255, 255);
}
}
public void dispose() {
if (!background.isDisposed()) background.dispose();
if (!foreground.isDisposed()) foreground.dispose();
if (!selectionBackground.isDisposed()) selectionBackground.dispose();
if (!selectionForeground.isDisposed()) selectionForeground.dispose();
}
}
// 使用颜色方案
ColorScheme lightScheme = new ColorScheme(display, "light");
shell.setBackground(lightScheme.background);
shell.setForeground(lightScheme.foreground);
费曼解释:自定义颜色方案的作用
自定义颜色方案 = 装修风格
- 北欧风(Light Mode):亮色,简洁
- 现代风(Dark Mode):深色,时尚
- 海洋风(Blue):蓝色,清新
类比:
自定义颜色方案 = 房间装修
- 北欧风:白色墙,浅色家具
- 现代风:黑色墙,深色家具
- 海洋风:蓝色墙,海浪家具
代码示例:主题切换
public class ThemeSwitcherExample {
private static ColorScheme currentScheme;
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("主题切换");
shell.setLayout(new GridLayout(1, false));
// 初始主题:Light
currentScheme = new ColorScheme(display, "light");
applyTheme(shell, currentScheme);
// 主题选择
Composite themePanel = new Composite(shell, SWT.NONE);
themePanel.setLayout(new FillLayout(SWT.HORIZONTAL));
Button lightButton = new Button(themePanel, SWT.PUSH);
lightButton.setText("Light");
lightButton.addListener(SWT.Selection, event -> {
if (currentScheme != null) currentScheme.dispose();
currentScheme = new ColorScheme(display, "light");
applyTheme(shell, currentScheme);
});
Button darkButton = new Button(themePanel, SWT.PUSH);
darkButton.setText("Dark");
darkButton.addListener(SWT.Selection, event -> {
if (currentScheme != null) currentScheme.dispose();
currentScheme = new ColorScheme(display, "dark");
applyTheme(shell, currentScheme);
});
Button blueButton = new Button(themePanel, SWT.PUSH);
blueButton.setText("Blue");
blueButton.addListener(SWT.Selection, event -> {
if (currentScheme != null) currentScheme.dispose();
currentScheme = new ColorScheme(display, "blue");
applyTheme(shell, currentScheme);
});
// 文本框
Text text = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
GridData textData = new GridData();
textData.horizontalAlignment = GridData.FILL;
textData.verticalAlignment = GridData.FILL;
textData.grabExcessHorizontalSpace = true;
textData.grabExcessVerticalSpace = true;
textData.heightHint = 150;
text.setLayoutData(textData);
text.setText("这是一个主题切换示例。\n\n点击上方的按钮切换不同的主题:\n- Light:浅色主题\n- Dark:深色主题\n- Blue:蓝色主题");
shell.setBounds(100, 100, 500, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
if (currentScheme != null) currentScheme.dispose();
display.dispose();
}
private static void applyTheme(Shell shell, ColorScheme scheme) {
shell.setBackground(scheme.background);
shell.setForeground(scheme.foreground);
// 应用到所有子控件
applyTheme(shell, scheme);
}
private static void applyTheme(Composite composite, ColorScheme scheme) {
Control[] children = composite.getChildren();
for (Control child : children) {
child.setBackground(scheme.background);
child.setForeground(scheme.foreground);
if (child instanceof Composite) {
applyTheme((Composite) child, scheme);
}
}
}
static class ColorScheme {
public Color background;
public Color foreground;
public ColorScheme(Display display, String name) {
if (name.equals("light")) {
background = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
foreground = display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND);
} else if (name.equals("dark")) {
background = new Color(display, 30, 30, 30);
foreground = new Color(display, 220, 220, 220);
} else if (name.equals("blue")) {
background = new Color(display, 240, 248, 255);
foreground = new Color(display, 0, 0, 0);
}
}
public void dispose() {
if (background != null && !background.isDisposed()) {
background.dispose();
}
if (foreground != null && !foreground.isDisposed()) {
foreground.dispose();
}
}
}
}
15.2 CSS 在 SWT 中的应用
使用 CSS 定义样式
注意:SWT 本身不直接支持 CSS,但可以使用第三方库或自己实现
// 简化的 CSS 解析器
class CSSParser {
public static Map<String, String> parse(String css) {
Map<String, String> styles = new HashMap<>();
String[] rules = css.split(";");
for (String rule : rules) {
String[] parts = rule.split(":");
if (parts.length == 2) {
String property = parts[0].trim();
String value = parts[1].trim();
styles.put(property, value);
}
}
return styles;
}
public static Color parseColor(Display display, String color) {
if (color.startsWith("#")) {
int r = Integer.parseInt(color.substring(1, 3), 16);
int g = Integer.parseInt(color.substring(3, 5), 16);
int b = Integer.parseInt(color.substring(5, 7), 16);
return new Color(display, r, g, b);
} else if (color.equals("red")) {
return display.getSystemColor(SWT.COLOR_RED);
} else if (color.equals("blue")) {
return display.getSystemColor(SWT.COLOR_BLUE);
} else if (color.equals("green")) {
return display.getSystemColor(SWT.COLOR_GREEN);
} else if (color.equals("white")) {
return display.getSystemColor(SWT.COLOR_WHITE);
} else if (color.equals("black")) {
return display.getSystemColor(SWT.COLOR_BLACK);
}
return null;
}
}
// 使用 CSS 样式
String css = "background-color: #f0f0f0; color: #333333; font-size: 12px;";
Map<String, String> styles = CSSParser.parse(css);
Color backgroundColor = CSSParser.parseColor(display, styles.get("background-color"));
Color foregroundColor = CSSParser.parseColor(display, styles.get("color"));
费曼解释:CSS 解析的原理
CSS 解析 = 翻译说明书
- CSS 说明书:background-color: #f0f0f0;
- 翻译器(CSSParser):解析 CSS
- 转换成 Java 对象:Color(240, 240, 240)
类比:
CSS 解析 = 翻译菜谱
- 菜谱:盐:5克
- 翻译器(CSSParser):解析菜谱
- 转换成 Java 对象:Salt(5)
动态切换主题
// 定义 CSS 主题
Map<String, String> lightTheme = new HashMap<>();
lightTheme.put("background-color", "#ffffff");
lightTheme.put("color", "#000000");
lightTheme.put("font-size", "12px");
Map<String, String> darkTheme = new HashMap<>();
darkTheme.put("background-color", "#1e1e1e");
darkTheme.put("color", "#ffffff");
darkTheme.put("font-size", "12px");
Map<String, String> currentTheme = lightTheme;
// 切换主题
Button darkModeButton = new Button(shell, SWT.PUSH);
darkModeButton.setText("Dark Mode");
darkModeButton.addListener(SWT.Selection, event -> {
currentTheme = darkTheme;
applyTheme(shell, currentTheme);
});
费曼解释:动态切换主题的原理
动态切换主题 = 换装修
- 原装修(Light Theme):白墙,黑字
- 新装修(Dark Theme):黑墙,白字
- 换装修 = 切换主题
类比:
动态切换主题 = 换衣服
- 原衣服(Light Theme):白 T 恤
- 新衣服(Dark Theme):黑 T 恤
- 换衣服 = 切换主题
代码示例:CSS 样式(简化版)
public class CSSThemeExample {
private static Map<String, String> currentTheme;
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("CSS 样式示例");
shell.setLayout(new GridLayout(1, false));
// 初始主题:Light
currentTheme = createLightTheme();
applyTheme(shell, currentTheme);
// 主题按钮
Composite themePanel = new Composite(shell, SWT.NONE);
themePanel.setLayout(new FillLayout(SWT.HORIZONTAL));
Button lightButton = new Button(themePanel, SWT.PUSH);
lightButton.setText("Light");
lightButton.addListener(SWT.Selection, event -> {
currentTheme = createLightTheme();
applyTheme(shell, currentTheme);
});
Button darkButton = new Button(themePanel, SWT.PUSH);
darkButton.setText("Dark");
darkButton.addListener(SWT.Selection, event -> {
currentTheme = createDarkTheme();
applyTheme(shell, currentTheme);
});
Button blueButton = new Button(themePanel, SWT.PUSH);
blueButton.setText("Blue");
blueButton.addListener(SWT.Selection, event -> {
currentTheme = createBlueTheme();
applyTheme(shell, currentTheme);
});
// 文本框
Text text = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
GridData textData = new GridData();
textData.horizontalAlignment = GridData.FILL;
textData.verticalAlignment = GridData.FILL;
textData.grabExcessHorizontalSpace = true;
textData.grabExcessVerticalSpace = true;
textData.heightHint = 150;
text.setLayoutData(textData);
text.setText("这是一个 CSS 样式示例。\n\n点击上方的按钮切换不同的主题:\n- Light:浅色主题\n- Dark:深色主题\n- Blue:蓝色主题\n\n注意:这是简化版的 CSS 解析器,实际应用中可以使用第三方库。");
shell.setBounds(100, 100, 500, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private static Map<String, String> createLightTheme() {
Map<String, String> theme = new HashMap<>();
theme.put("background-color", "#ffffff");
theme.put("color", "#000000");
theme.put("font-size", "12");
return theme;
}
private static Map<String, String> createDarkTheme() {
Map<String, String> theme = new HashMap<>();
theme.put("background-color", "#1e1e1e");
theme.put("color", "#ffffff");
theme.put("font-size", "12");
return theme;
}
private static Map<String, String> createBlueTheme() {
Map<String, String> theme = new HashMap<>();
theme.put("background-color", "#e3f2fd");
theme.put("color", "#000000");
theme.put("font-size", "12");
return theme;
}
private static void applyTheme(Shell shell, Map<String, String> theme) {
Display display = shell.getDisplay();
Color backgroundColor = parseColor(display, theme.get("background-color"));
Color foregroundColor = parseColor(display, theme.get("color"));
int fontSize = Integer.parseInt(theme.get("font-size"));
shell.setBackground(backgroundColor);
shell.setForeground(foregroundColor);
shell.setFont(new Font(display, "Arial", fontSize, SWT.NORMAL));
// 应用到所有子控件
applyTheme(shell, theme);
}
private static void applyTheme(Composite composite, Map<String, String> theme) {
Display display = composite.getDisplay();
Color backgroundColor = parseColor(display, theme.get("background-color"));
Color foregroundColor = parseColor(display, theme.get("color"));
int fontSize = Integer.parseInt(theme.get("font-size"));
Control[] children = composite.getChildren();
for (Control child : children) {
child.setBackground(backgroundColor);
child.setForeground(foregroundColor);
child.setFont(new Font(display, "Arial", fontSize, SWT.NORMAL));
if (child instanceof Composite) {
applyTheme((Composite) child, theme);
}
}
}
private static Color parseColor(Display display, String color) {
if (color == null) return null;
if (color.startsWith("#")) {
int r = Integer.parseInt(color.substring(1, 3), 16);
int g = Integer.parseInt(color.substring(3, 5), 16);
int b = Integer.parseInt(color.substring(5, 7), 16);
return new Color(display, r, g, b);
} else if (color.equals("red")) {
return display.getSystemColor(SWT.COLOR_RED);
} else if (color.equals("blue")) {
return display.getSystemColor(SWT.COLOR_BLUE);
} else if (color.equals("green")) {
return display.getSystemColor(SWT.COLOR_GREEN);
} else if (color.equals("white")) {
return display.getSystemColor(SWT.COLOR_WHITE);
} else if (color.equals("black")) {
return display.getSystemColor(SWT.COLOR_BLACK);
}
return null;
}
}
15.3 图标主题
SVG 图标的支持
注意:SWT 本身不直接支持 SVG,但可以使用第三方库如 Apache Batik
// 使用 Batik 库加载 SVG
import org.apache.batik.dom.svg.SVGDOMRootElement;
import org.apache.batik.svggen.SVGGraphics2D;
import org.w3c.dom.svg.SVGDocument;
public class SVGIconLoader {
public static Image loadSVGImage(Display display, String svgPath) {
try {
// 加载 SVG 文档
String parser = XMLResourceDescriptor.getXMLParserClassName();
SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
SVGDocument doc = f.createSVGDocument(new File(svgPath).toURI());
// 渲染 SVG 到 Image
SVGGraphics2D g2d = new SVGGraphics2D(true);
g2d.setSVGCanvasSize(doc.getRootElement().getBBox());
// 创建 Image
Image image = new Image(display, g2d.getSVGCanvasSize().width,
g2d.getSVGCanvasSize().height);
GC gc = new GC(image);
g2d.setGC(gc);
g2d.renderTree(doc);
g2d.dispose();
gc.dispose();
return image;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
// 使用 SVG 图标
Image svgIcon = SVGIconLoader.loadSVGImage(display, "icon.svg");
shell.setImage(svgIcon);
费曼解释:SVG 图标的原理
SVG 图标 = 矢量图形
- SVG 用数学公式描述图形(矢量)
- 放大不失真(无限分辨率)
- PNG 用像素描述图形(位图)
- 放大会失真(固定分辨率)
类比:
SVG 图标 = 数学函数
- y = x^2(函数)
- 无论放大多少倍,曲线都平滑
PNG 图标 = 像素网格
- 100x100 的网格(像素)
- 放大到 200x200,网格变模糊
多尺寸适配
// 加载不同尺寸的图标
Image icon16 = new Image(display, "icon16.png");
Image icon32 = new Image(display, "icon32.png");
Image icon48 = new Image(display, "icon48.png");
Image icon64 = new Image(display, "icon64.png");
Image icon128 = new Image(display, "icon128.png");
// 根据屏幕 DPI 选择图标
int dpi = getDPI();
if (dpi <= 96) {
shell.setImage(icon16);
} else if (dpi <= 120) {
shell.setImage(icon32);
} else if (dpi <= 144) {
shell.setImage(icon48);
} else if (dpi <= 192) {
shell.setImage(icon64);
} else {
shell.setImage(icon128);
}
// 释放图标
icon16.dispose();
icon32.dispose();
icon48.dispose();
icon64.dispose();
icon128.dispose();
费曼解释:多尺寸适配的原理
多尺寸适配 = 尺码齐全的服装
- 1.6 米:S 号
- 1.7 米:M 号
- 1.8 米:L 号
- 1.9 米:XL 号
- 根据身高选择尺码
类比:
多尺寸适配 = 鞋码齐全的鞋店
- 38 码:适合 24 厘米脚
- 40 码:适合 25 厘米脚
- 42 码:适合 26 厘米脚
- 根据脚长选择鞋码
代码示例:图标主题
public class IconThemeExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("图标主题");
shell.setLayout(new GridLayout(1, false));
// 图标主题选择
Composite iconPanel = new Composite(shell, SWT.NONE);
iconPanel.setLayout(new FillLayout(SWT.HORIZONTAL));
Button colorButton = new Button(iconPanel, SWT.PUSH);
colorButton.setText("彩色");
colorButton.addListener(SWT.Selection, event -> {
// 注意:实际应用中应该从文件加载图标
// shell.setImage(colorIcon);
System.out.println("切换到彩色图标");
});
Button grayButton = new Button(iconPanel, SWT.PUSH);
grayButton.setText("灰色");
grayButton.addListener(SWT.Selection, event -> {
// 注意:实际应用中应该从文件加载图标
// shell.setImage(grayIcon);
System.out.println("切换到灰色图标");
});
Button highDPIButton = new Button(iconPanel, SWT.PUSH);
highDPIButton.setText("High DPI");
highDPIButton.addListener(SWT.Selection, event -> {
// 注意:实际应用中应该根据 DPI 选择不同尺寸的图标
// int dpi = getDPI();
// if (dpi >= 144) {
// shell.setImage(icon48);
// } else {
// shell.setImage(icon16);
// }
System.out.println("切换到 High DPI 图标");
});
// 文本框:显示信息
Text text = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
GridData textData = new GridData();
textData.horizontalAlignment = GridData.FILL;
textData.verticalAlignment = GridData.FILL;
textData.grabExcessHorizontalSpace = true;
textData.grabExcessVerticalSpace = true;
textData.heightHint = 150;
text.setLayoutData(textData);
text.setText("这是一个图标主题示例。\n\n点击上方的按钮切换不同的图标主题:\n- 彩色:彩色图标\n- 灰色:灰色图标\n- High DPI:根据 DPI 选择不同尺寸的图标\n\n注意:这是简化版,实际应用中应该从文件加载不同尺寸的图标。");
shell.setBounds(100, 100, 500, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
// 简化的 DPI 检测
private static int getDPI() {
// 注意:实际应用中应该使用 JNA 检测系统 DPI
return 96; // 默认 96 DPI
}
}
15.4 本章小结
主题与样式总结
| 概念 | 作用 | 类比 |
|---|---|---|
| Dark Mode | 深色主题 | 夜间模式 |
| ColorScheme | 颜色方案 | 装修风格 |
| CSS | 样式表 | 说明书 |
| SVG 图标 | 矢量图形 | 数学函数 |
| 多尺寸适配 | DPI 适配 | 鞋码齐全 |
费曼测试:你能解释清楚吗?
- Dark Mode 的作用?
- 深色背景,浅色文字,保护眼睛
- 自定义颜色方案的原理?
- 定义背景色、前景色、选中色等,形成整体风格
- CSS 解析的作用?
- 将 CSS 样式转换成 Java 对象
- SVG 图标的优势?
- 矢量图形,放大不失真
下一章预告
现在你已经掌握了主题与样式,
可以支持 Dark Mode,
提升用户体验。
下一章,我们将学习 SWT 应用的打包与部署,
让应用可以分发到用户手中,
真正投入使用。
练习题:
- 创建一个应用,支持 Light、Dark、Blue 三种主题切换。
- 创建一个简化的 CSS 解析器,解析 background-color、color 属性。
- 创建一个应用,支持根据 DPI 选择不同尺寸的图标(模拟)。
(提示:使用 ColorScheme 类管理颜色,使用 Map 存储主题)