refactor: simplify icon assets and improve keystore resolution
- Remove separate icon_foreground.png, reuse icon.png for adaptive icon foreground - Enhance resolveKeystoreFile() with root-relative → module-relative fallback - Update documentation to reflect icon consolidation - Update test bootstrap IP to 192.168.2.57
|
|
@ -33,6 +33,10 @@ web_android_shell/
|
|||
└── architecture.md # 架构说明
|
||||
```
|
||||
|
||||
## 执行指令
|
||||
```text
|
||||
dart run tool/generate_app.dart aixue
|
||||
```
|
||||
## 核心思路
|
||||
|
||||
- 应用启动优先读取本地默认启动配置 `assets/config/bootstrap.json`
|
||||
|
|
@ -130,7 +134,6 @@ theme:
|
|||
branding:
|
||||
icon: "icon.png"
|
||||
icon_background: "#FFFFFF"
|
||||
icon_foreground: "icon_foreground.png"
|
||||
splash: "splash.png"
|
||||
splash_color: "#FFFFFF"
|
||||
```
|
||||
|
|
@ -145,6 +148,7 @@ branding:
|
|||
- `portraitDown`
|
||||
- `landscapeLeft`
|
||||
- `landscapeRight`
|
||||
- `branding.icon`:同时用于普通应用图标和 Android 自适应图标前景
|
||||
|
||||
## 生成后的入口形态
|
||||
|
||||
|
|
@ -187,8 +191,7 @@ runShellApp(
|
|||
|
||||
| 资源 | 文件名 | 最小尺寸 | 格式 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| 应用图标 | `icon.png` | 1024×1024 | PNG | 用于生成各尺寸 launcher icon |
|
||||
| 自适应图标前景 | `icon_foreground.png` | 1024×1024 | PNG | 建议透明背景,主体留安全区 |
|
||||
| 应用图标 | `icon.png` | 1024×1024 | PNG | 同时用于生成各尺寸 launcher icon 与 Android 自适应图标前景 |
|
||||
| 启动页图片 | `splash.png` | 1152×1152 | PNG | 用于生成 Android 12+ 与旧版启动页 |
|
||||
|
||||
品牌资源源文件放在 `flavors/<品牌名>/` 下,生成后会复制到 `apps/<品牌名>/assets/branding/`。
|
||||
|
|
|
|||
|
|
@ -30,7 +30,21 @@ fun resolveKeystoreFile(rawPath: String?): File {
|
|||
|
||||
val normalized = expandedHome.replace('\\', File.separatorChar).replace('/', File.separatorChar)
|
||||
val storeFile = File(normalized)
|
||||
return if (storeFile.isAbsolute) storeFile else rootProject.file(normalized)
|
||||
if (storeFile.isAbsolute) {
|
||||
return storeFile
|
||||
}
|
||||
|
||||
val rootRelativeFile = rootProject.file(normalized)
|
||||
if (rootRelativeFile.exists()) {
|
||||
return rootRelativeFile
|
||||
}
|
||||
|
||||
val moduleRelativeFile = project.file(normalized)
|
||||
if (moduleRelativeFile.exists()) {
|
||||
return moduleRelativeFile
|
||||
}
|
||||
|
||||
return rootRelativeFile
|
||||
}
|
||||
|
||||
val releaseStoreFile =
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 490 KiB |
|
|
@ -2,4 +2,4 @@ flutter_launcher_icons:
|
|||
android: true
|
||||
image_path: "assets/branding/icon.png"
|
||||
adaptive_icon_background: "#FFFFFF"
|
||||
adaptive_icon_foreground: "assets/branding/icon_foreground.png"
|
||||
adaptive_icon_foreground: "assets/branding/icon.png"
|
||||
|
|
|
|||
|
|
@ -30,7 +30,21 @@ fun resolveKeystoreFile(rawPath: String?): File {
|
|||
|
||||
val normalized = expandedHome.replace('\\', File.separatorChar).replace('/', File.separatorChar)
|
||||
val storeFile = File(normalized)
|
||||
return if (storeFile.isAbsolute) storeFile else rootProject.file(normalized)
|
||||
if (storeFile.isAbsolute) {
|
||||
return storeFile
|
||||
}
|
||||
|
||||
val rootRelativeFile = rootProject.file(normalized)
|
||||
if (rootRelativeFile.exists()) {
|
||||
return rootRelativeFile
|
||||
}
|
||||
|
||||
val moduleRelativeFile = project.file(normalized)
|
||||
if (moduleRelativeFile.exists()) {
|
||||
return moduleRelativeFile
|
||||
}
|
||||
|
||||
return rootRelativeFile
|
||||
}
|
||||
|
||||
val releaseStoreFile =
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 29 KiB |
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"initialUrl": "http://192.168.2.54:8080/test_bridge.html",
|
||||
"initialUrl": "http://192.168.2.57:8080/test_bridge.html",
|
||||
"preferredOrientations": [
|
||||
"portraitUp",
|
||||
"portraitDown"
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ flutter_launcher_icons:
|
|||
android: true
|
||||
image_path: "assets/branding/icon.png"
|
||||
adaptive_icon_background: "#1F2937"
|
||||
adaptive_icon_foreground: "assets/branding/icon_foreground.png"
|
||||
adaptive_icon_foreground: "assets/branding/icon.png"
|
||||
|
|
|
|||
|
|
@ -30,7 +30,21 @@ fun resolveKeystoreFile(rawPath: String?): File {
|
|||
|
||||
val normalized = expandedHome.replace('\\', File.separatorChar).replace('/', File.separatorChar)
|
||||
val storeFile = File(normalized)
|
||||
return if (storeFile.isAbsolute) storeFile else rootProject.file(normalized)
|
||||
if (storeFile.isAbsolute) {
|
||||
return storeFile
|
||||
}
|
||||
|
||||
val rootRelativeFile = rootProject.file(normalized)
|
||||
if (rootRelativeFile.exists()) {
|
||||
return rootRelativeFile
|
||||
}
|
||||
|
||||
val moduleRelativeFile = project.file(normalized)
|
||||
if (moduleRelativeFile.exists()) {
|
||||
return moduleRelativeFile
|
||||
}
|
||||
|
||||
return rootRelativeFile
|
||||
}
|
||||
|
||||
val releaseStoreFile =
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 30 KiB |
|
|
@ -2,4 +2,4 @@ flutter_launcher_icons:
|
|||
android: true
|
||||
image_path: "assets/branding/icon.png"
|
||||
adaptive_icon_background: "#FFFFFF"
|
||||
adaptive_icon_foreground: "assets/branding/icon_foreground.png"
|
||||
adaptive_icon_foreground: "assets/branding/icon.png"
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 48 KiB |
|
|
@ -10,8 +10,7 @@
|
|||
|
||||
| 资源名称 | 文件名 | 格式要求 | 尺寸 (px) | 核心要求 |
|
||||
|---|---|---|---|---|
|
||||
| **应用图标** | `icon.png` | PNG *(不透明)* | 1024 × 1024 | 正方形铺满(用于老版本系统图标)。 |
|
||||
| **自适应前景** | `icon_foreground.png` | **PNG (背景透明)** | 1024 × 1024 | 主体图形居中。四周必须保留 **33% 的透明安全区**。 |
|
||||
| **应用图标** | `icon.png` | PNG *(不透明)* | 1024 × 1024 | 同时用于老版本系统图标与 Android 自适应图标前景。 |
|
||||
| **启动页图像** | `splash.png` | PNG *(含透明或实色)* | 1152 × 1152 | 核心 Logo 必须完全置于正中心的 **768×768 直径圆形**范围内。 |
|
||||
| **单色前景** *(可选)* | `icon_monochrome.png` | **PNG (背景透明)** | 1024 × 1024 | 专为 Android 13+ 提供。必须是纯黑色或纯白色(无渐变),靠透明度展现轮廓。 |
|
||||
|
||||
|
|
@ -21,30 +20,19 @@
|
|||
|
||||
### 2.1 应用图标 (`icon.png`)
|
||||
|
||||
这是最基础的图标,用于向后兼容较老版本的 Android 系统。
|
||||
这是最基础的图标,同时用于向后兼容较老版本的 Android 系统,以及生成 Android 自适应图标前景。
|
||||
|
||||
* **尺寸**:1024 × 1024 像素(也可提供最低 512 × 512 的设计底线)。
|
||||
* **要求**:不需要圆角或圆形裁切(系统会自动裁切),直接提交带有背景颜色的**纯正方向**图形。
|
||||
* **自适应图标兼容建议**:
|
||||
* 主体内容尽量集中在中央安全区,避免过分贴边。
|
||||
* 如图标本身包含文字或复杂形状,建议四周保留适当留白,避免被系统遮罩裁切。
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### 2.2 自适应图标前景 (`icon_foreground.png`)
|
||||
|
||||
自 Android 8.0 起,系统采用了由「背景层」+「前景层」自由组合实现动态效果的自适应图标(Adaptive Icon)。该图即为此时使用的「前景层」。
|
||||
|
||||
* **格式**:**必须是带透明背景的 PNG (PNG-24/32)**,不能使用 JPEG,否则其白色底会遮盖住背景颜色。
|
||||
* **安全区设计**:
|
||||
* 画布总尺寸保持 1024 × 1024。
|
||||
* **主体内容必须集中在中央的 682 × 682 的圆形安全区内**。
|
||||
* 外围的透明留白(约占据画布边长的 33%)将被系统底层用于视差动画和异形遮罩裁切。超出中央安全圈的内容**将被无情裁掉**。
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### 2.3 启动页图片 (`splash.png`)
|
||||
### 2.2 启动页图片 (`splash.png`)
|
||||
|
||||
自 Android 12 开始,系统强制接管开屏动画(SplashScreen API),对核心图像的位置和大小有极其严苛的要求,否则在 2K/4K 等高清分辨率平板上会造成拉伸模糊或主体被截断。
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,5 @@ theme:
|
|||
branding:
|
||||
icon: "icon.png"
|
||||
icon_background: "#FFFFFF"
|
||||
icon_foreground: "icon_foreground.png"
|
||||
splash: "splash.png"
|
||||
splash_color: "#FFFFFF"
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 490 KiB |
|
|
@ -15,6 +15,5 @@ theme:
|
|||
branding:
|
||||
icon: "icon.png"
|
||||
icon_background: "#1F2937"
|
||||
icon_foreground: "icon_foreground.png"
|
||||
splash: "splash.png"
|
||||
splash_color: "#1F2937"
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 29 KiB |
|
|
@ -15,6 +15,5 @@ theme:
|
|||
branding:
|
||||
icon: "icon.png"
|
||||
icon_background: "#FFFFFF"
|
||||
icon_foreground: "icon_foreground.png"
|
||||
splash: "splash.png"
|
||||
splash_color: "#FFFFFF"
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 30 KiB |
|
|
@ -30,7 +30,21 @@ fun resolveKeystoreFile(rawPath: String?): File {
|
|||
|
||||
val normalized = expandedHome.replace('\\', File.separatorChar).replace('/', File.separatorChar)
|
||||
val storeFile = File(normalized)
|
||||
return if (storeFile.isAbsolute) storeFile else rootProject.file(normalized)
|
||||
if (storeFile.isAbsolute) {
|
||||
return storeFile
|
||||
}
|
||||
|
||||
val rootRelativeFile = rootProject.file(normalized)
|
||||
if (rootRelativeFile.exists()) {
|
||||
return rootRelativeFile
|
||||
}
|
||||
|
||||
val moduleRelativeFile = project.file(normalized)
|
||||
if (moduleRelativeFile.exists()) {
|
||||
return moduleRelativeFile
|
||||
}
|
||||
|
||||
return rootRelativeFile
|
||||
}
|
||||
|
||||
val releaseStoreFile = resolveKeystoreFile(keystoreProperties["storeFile"] as String?)
|
||||
|
|
|
|||
|
|
@ -515,7 +515,6 @@ Future<void> _generateBrandingAssets(
|
|||
], workingDirectory: appDir);
|
||||
|
||||
final iconPath = 'assets/branding/${branding['icon']}';
|
||||
final iconForeground = 'assets/branding/${branding['icon_foreground']}';
|
||||
final iconBackground = branding['icon_background'] as String;
|
||||
final iconsYaml =
|
||||
'''
|
||||
|
|
@ -523,7 +522,7 @@ flutter_launcher_icons:
|
|||
android: true
|
||||
image_path: "$iconPath"
|
||||
adaptive_icon_background: "$iconBackground"
|
||||
adaptive_icon_foreground: "$iconForeground"
|
||||
adaptive_icon_foreground: "$iconPath"
|
||||
''';
|
||||
await File('$appDir/flutter_launcher_icons.yaml').writeAsString(iconsYaml);
|
||||
|
||||
|
|
@ -652,9 +651,23 @@ fun resolveKeystoreFile(rawPath: String?): File {
|
|||
candidate
|
||||
}
|
||||
|
||||
val normalized = expandedHome.replace('\\', File.separatorChar).replace('/', File.separatorChar)
|
||||
val normalized = expandedHome.replace('\\\\', File.separatorChar).replace('/', File.separatorChar)
|
||||
val storeFile = File(normalized)
|
||||
return if (storeFile.isAbsolute) storeFile else rootProject.file(normalized)
|
||||
if (storeFile.isAbsolute) {
|
||||
return storeFile
|
||||
}
|
||||
|
||||
val rootRelativeFile = rootProject.file(normalized)
|
||||
if (rootRelativeFile.exists()) {
|
||||
return rootRelativeFile
|
||||
}
|
||||
|
||||
val moduleRelativeFile = project.file(normalized)
|
||||
if (moduleRelativeFile.exists()) {
|
||||
return moduleRelativeFile
|
||||
}
|
||||
|
||||
return rootRelativeFile
|
||||
}
|
||||
|
||||
val releaseStoreFile =
|
||||
|
|
|
|||