主题

主题(Themes)用于管理应用程序的可视化展示。

一个主题由一些 CSS 文件和其他资源构成,比如图片、字体等。

Jmix 提供了基于 Vaadin Lumo 主题构建的 jmix-lumo 主题。

使用主题

应用程序主题通过 @Theme 注解提供,注解中使用主题文件夹的名称作为参数。@Theme 注解必须配置在实现了 AppShellConfigurator 接口的类上,通常为主应用程序类。

@Theme(value = "my-theme")
public class MyProjectApplication implements AppShellConfigurator {
    ...
}

主题的样式版本(variant)可以作为附加参数,例如 darklight

@Theme(value = "my-theme", variant = "dark")
public class MyProjectApplication implements AppShellConfigurator {
    ...
}
主题不能在运行时切换。尽管一个项目可以有多个主题,但是在 UI 中只能使用一个主题。然而,同一个主题中的样式版本可以 动态切换,并且可以在主题的基础上 动态加载额外的样式

应用程序主题

在应用程序中,主题的文件夹在 src/main/frontend/themes。使用 Studio 创建的 Jmix 应用程序项目有一个预定义的主题文件夹,其名称与项目本身相同。

示例:

src
└── main
    └── frontend
        └── themes
            └── my-project
                ├── my-project.css
                ├── styles.css
                ├── theme.json
                └── view
                    ├── login-view.css
                    ├── main-view-top-menu.css
                    └── main-view.css
  • my-project - 主题文件夹。其名称作为 @Theme 注解的主题名称。

  • my-project.css - 该主题自定义样式的样式表。

  • styles.css - 主样式表,是该主题样式的入口点。

  • theme.json - 主题配置文件。默认情况下,将 jmix-lumo 定义为父主题。

  • view - 此文件夹包含模板项目提供的登录视图和主视图样式的 CSS 文件。

所有 CSS,包括 CSS 变量值和自定义组件样式,都可以包含在主 styles.css 文件中。然而,默认情况下,该文件仅包含导入指令,实际的 CSS 一般在单独的文件中,更易于管理:

styles.css
@import url('my-project.css');
@import url('view/main-view.css');
@import url('view/main-view-top-menu.css');
@import url('view/login-view.css');

将项目的 CSS 放在 <theme_name>.css 文件(示例中为 my-project.css)。

使用 Vaadin 的在线 Lumo 主题编辑器 可以快速自定义应用程序的主题。

要选择所需的 UI 组件外观,可以使用右侧面板中的控件。然后,点击 Download 并将生成的 CSS 代码(<style> 标签内的部分)插入到你的 <theme_name>.css 文件中。

可重用主题

应用程序的主题可以打包成独立的 JAR 文件,从而提供给多给项目使用。

主题项目的结构如下:

src
└── main
    └── resources
        └── META-INF
            └── resources
                └── themes
                    └── cobalt
                        ├── styles.css
                        └── theme.json
  • cobalt - 主题文件夹,也用作主题名称

  • styles.css - 主样式文件。可以包含自定义样式和导入,示例:

    html {
        --lumo-border-radius: calc(var(--lumo-size-m) / 2);
    
        --lumo-primary-color: rgb(0, 85, 166);
        --lumo-primary-color-50pct: rgba(0, 85, 166, 0.5);
        --lumo-primary-color-10pct: rgba(0, 85, 166, 0.1);
        --lumo-primary-text-color: rgb(0, 85, 166);
    }
  • theme.json - 主题配置文件。建议使用 jmix-lumo 作为父主题,这样自定义的主题可以使用 jmix-lumo 中的默认样式渲染 Jmix UI 组件和视图。示例:

    {
      "parent": "jmix-lumo",
      "lumoImports":["typography","color","spacing","badge","utility"]
    }

主题项目中的 build.gradle 文件如下:

plugins {
    id 'java'
    id 'com.gradle.plugin-publish' version '1.2.1'
}

group = 'com.company'
version = '0.0.1-SNAPSHOT'

repositories {
    mavenCentral()
    maven {
        url 'https://global.repo.jmix.io/repository/public'
    }
}

dependencies {
    implementation 'io.jmix.flowui:jmix-flowui-themes:2.4.0' (1)
}
1 依赖 jmix-flowui-themes,其中包含 jmix-lumo 主题。

在项目中添加了这个主题 JAR 之后,可以使用 JAR 中打包的主题:

@Theme(value = "cobalt")
public class MyProjectApplication implements AppShellConfigurator {
    ...
}

或者也可以作为项目主题的父主题,示例:

theme.json
{
  "parent": "cobalt",
  "lumoImports":["typography","color","spacing","badge","utility"]
}

这样项目中的主题会基于该主题扩展。

custom theme
Figure 1. 自定义主题示例

可插拔样式

当开发一个 自定义扩展组件 时,可以在 src/main/resources/META-INF/resources/ 目录创建包含自定义样式的文件。如需在最终的应用程序中包含这些样式,可以在 module.properties 文件中定义 jmix.ui.export-styles 属性。该属性的值是 src/main/resources/META-INF/resources/ 下的相对路径,例如,jmix.ui.export-styles = addon-styles/my-addon-styles.css

my-addon-styles.css
.test {
    color: red;
}

自定义样式会作为 <style type="text/css"> 添加到 <head> 元素中:

<style type="text/css">
.test {
    color: red;
}
</style>
这种方式仅适用于少量无法用到特定 UI 组件的样式。例如,扩展组件内视图的 CSS 工具类。

运行时更改主题

为了实现在不同的样式版本之间切换,例如 lightdark,框架提供了一个与 JavaScript 文件结合使用的静态帮助类。

ThemeUtils 类提供了用于切换样式版本的方法,主要是在 Web Local Storage 中设置样式版本,然后调用 JavaScript 代码使其生效。

例如,可以在主视图中添加一个 dropdownButton 用于切换不同的主题:

main-view.xml
<dropdownButton id="themeSwitcher"
                text="Theme" icon="ADJUST"
                classNames="ms-auto me-m"
                dropdownIndicatorVisible="false">
    <items>
        <actionItem id="systemThemeItem">
            <action id="systemThemeAction"
                    text="System" icon="ADJUST"/>
        </actionItem>
        <actionItem id="lightThemeItem">
            <action id="lightThemeAction"
                    text="Light" icon="SUN_O"/>
        </actionItem>
        <actionItem id="darkThemeItem">
            <action id="darkThemeAction"
                    text="Dark" icon="MOON_O"/>
        </actionItem>
    </items>
</dropdownButton>
MainView.java
@Subscribe("themeSwitcher.systemThemeItem.systemThemeAction")
public void onThemeSwitcherSystemThemeItemSystemThemeAction(final ActionPerformedEvent event) {
    ThemeUtils.applySystemTheme();
}

@Subscribe("themeSwitcher.lightThemeItem.lightThemeAction")
public void onThemeSwitcherLightThemeItemLightThemeAction(final ActionPerformedEvent event) {
    ThemeUtils.applyLightTheme();
}

@Subscribe("themeSwitcher.darkThemeItem.darkThemeAction")
public void onThemeSwitcherDarkThemeItemDarkThemeAction(final ActionPerformedEvent event) {
    ThemeUtils.applyDarkTheme();
}
theme switcher
Figure 2. ThemeSwitcher 组件

JavaScript 文件中的代码根据 Web Local Storage 中的值在 HTML 中使用特定的主题版本,并订阅 prefers-color-scheme:在用户通过操作系统设置(例如,浅色或深色模式)或 user agent 设置主题偏好时可以更新主题。

需要在主应用程序类中 import:

@JsModule("./src/theme/color-scheme-switching-support.js")
@SpringBootApplication
public class OnboardingApplication implements AppShellConfigurator {
theme switcher

为不同样式版本自定义组件样式

当允许用户在运行时切换浅色和深色主题时,可能需要为每个主题定义特定的样式,以保证界面适当的对比度和视觉外观。

如需按照当前激活的主题有条件地变更样式,需要在 CSS 中使用 html 元素上的 theme 属性。当主题变化时,Jmix 会向根 html 元素添加相应的 theme 属性。

  • 对于浅色,属性为 theme="light"

  • 对于深色,属性为 theme="dark"

可以在主题的 CSS 文件(例如 my-project.cssstyles.css)中使用这些选择器,为特定主题覆盖组件样式。

假设有一个自定义样式名为 my-button 的按钮,并且希望在浅色和深色模式下具有不同的文本颜色:

/* Default style for light mode */
vaadin-button.my-button {
    color: navy;
}

/* Override for dark mode */
html[theme='dark'] vaadin-button.my-button {
    color: lightskyblue;
}

也可以覆盖 Lumo 内置的主题 CSS 属性(变量)。这样可以一次统一修改许多组件的外观。

例如,仅修改深色模式模式中的主色(primary color):

html[theme='dark'] {
    --lumo-primary-color: rgb(100, 200, 255);
    --lumo-primary-text-color: rgb(100, 200, 255);
}
  • 将这些特定于某个主题的样式放在主题的主 CSS 文件中(例如 my-project.css)。html[theme='dark'] 选择器仅在使用深色主题时才能匹配。

  • 确保深色样式不会与浅色中的样式冲突或无意中覆盖。