Skip to content

Commit f3de40f

Browse files
committed
feat: 添加字体配置管理功能,支持用户选择和配置不同组件的字体
1 parent 6e242a8 commit f3de40f

11 files changed

Lines changed: 546 additions & 3 deletions

File tree

FONT_FEATURE_README.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Fcitx5 Android 字体配置功能实现
2+
3+
## 概述
4+
5+
本功能为 Fcitx5-android 项目添加了完整的字体配置管理功能,允许用户在设置界面中直接选择和配置不同组件的字体。
6+
7+
## 功能特性
8+
9+
### 支持的字体配置项
10+
- **候选字体** (`cand_font`): 候选词显示字体
11+
- **编码字体** (`preedit_font`): 输入编码显示字体
12+
- **弹出按键字体** (`popup_key_font`): 弹出键盘按键字体
13+
- **按键主字体** (`key_main_font`): 主要按键文字字体
14+
- **按键次字体** (`key_alt_font`): 按键辅助文字字体
15+
- **默认字体** (`font`): AutoScaleTextView 默认字体
16+
17+
### 核心功能
18+
1. **字体文件管理**: 自动扫描 `fonts/` 目录下的 `.ttf``.otf` 字体文件
19+
2. **实时配置**: 配置更改后立即生效,无需重启应用
20+
3. **配置持久化**: 自动保存配置到 `fonts/fontset.json` 文件
21+
4. **系统默认支持**: 可选择使用系统默认字体
22+
23+
## 实现细节
24+
25+
### 文件结构
26+
27+
```
28+
app/src/main/java/org/fcitx/fcitx5/android/
29+
├── data/
30+
│ ├── fonts/
31+
│ │ ├── FontManager.kt # 字体管理核心类
32+
│ │ └── FontConfigTest.kt # 测试工具类
33+
│ └── prefs/
34+
│ └── AppPrefs.kt # 添加了 Fonts 配置类
35+
└── ui/main/settings/behavior/
36+
└── FontsSettingsFragment.kt # 字体设置界面
37+
```
38+
39+
### 核心类说明
40+
41+
#### FontManager
42+
- 管理字体文件扫描和字体选择对话框
43+
- 处理字体配置的读写和更新
44+
- 提供字体缓存清理功能
45+
46+
#### AppPrefs.Fonts
47+
- 管理字体配置的 SharedPreferences 存储
48+
- 监听配置更改并触发自动更新
49+
50+
#### FontsSettingsFragment
51+
- 提供用户友好的字体选择界面
52+
- 显示当前选择的字体名称
53+
- 集成字体选择对话框
54+
55+
### 配置文件格式
56+
57+
字体配置保存在 `fonts/fontset.json` 文件中:
58+
59+
```json
60+
{
61+
"cand_font": "LXGWWenKai-Regular.ttf",
62+
"font": "",
63+
"preedit_font": "segoepr.ttf",
64+
"popup_key_font": "segoepr.ttf",
65+
"key_main_font": "segoepr.ttf",
66+
"key_alt_font": "segoepr.ttf"
67+
}
68+
```
69+
70+
空字符串表示使用系统默认字体。
71+
72+
## 使用方法
73+
74+
### 用户操作
75+
1. 将字体文件 (`.ttf``.otf`) 放入应用的 `fonts/` 目录
76+
2. 打开 Fcitx5 设置 → 字体
77+
3. 点击要配置的字体项目
78+
4. 从弹出的列表中选择字体
79+
5. 配置立即生效
80+
81+
### 开发者集成
82+
```kotlin
83+
// 手动更新字体配置
84+
FontManager.updateFontConfiguration()
85+
86+
// 获取可用字体列表
87+
val fonts = FontManager.getAvailableFonts()
88+
89+
// 显示字体选择对话框
90+
FontManager.showFontPickerDialog(context, R.string.font_title) { selectedFont ->
91+
// 处理选择的字体
92+
}
93+
```
94+
95+
## 技术实现亮点
96+
97+
1. **模块化设计**: 字体管理功能独立模块,易于维护和扩展
98+
2. **异常安全**: 完善的错误处理,避免因字体配置问题导致应用崩溃
99+
3. **性能优化**: 字体缓存机制,避免重复加载
100+
4. **用户体验**: 实时预览字体名称,直观的选择界面
101+
5. **向后兼容**: 兼容现有的字体配置文件格式
102+
103+
## 国际化支持
104+
105+
已添加中英文字符串资源:
106+
- 英文: `values/strings.xml`
107+
- 简体中文: `values-zh-rCN/strings.xml`
108+
109+
## 测试
110+
111+
使用 `FontConfigTest` 类进行功能验证:
112+
```kotlin
113+
// 创建测试配置
114+
FontConfigTest.createTestFontConfig()
115+
116+
// 验证配置正确性
117+
val isValid = FontConfigTest.validateFontConfig()
118+
```
119+
120+
## 注意事项
121+
122+
1. 字体文件需要放在应用的外部存储 `fonts/` 目录下
123+
2. 支持的字体格式:`.ttf`, `.otf`
124+
3. 配置更改后会自动清理字体缓存以确保更改生效
125+
4. 空字体文件名表示使用系统默认字体
126+
127+
## 未来扩展
128+
129+
- 字体预览功能
130+
- 字体大小配置
131+
- 字体样式配置(粗体、斜体等)
132+
- 字体文件在线下载功能
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* SPDX-FileCopyrightText: Copyright 2025 Fcitx5 for Android Contributors
4+
*/
5+
package org.fcitx.fcitx5.android.data.fonts
6+
7+
import org.fcitx.fcitx5.android.utils.appContext
8+
import org.json.JSONObject
9+
import java.io.File
10+
11+
/**
12+
* 字体配置测试工具类
13+
*/
14+
object FontConfigTest {
15+
16+
/**
17+
* 创建一个测试字体配置
18+
*/
19+
fun createTestFontConfig() {
20+
val fontsDir = File(appContext.getExternalFilesDir(null), "fonts")
21+
if (!fontsDir.exists()) {
22+
fontsDir.mkdirs()
23+
}
24+
25+
val configFile = File(fontsDir, "fontset.json")
26+
val testConfig = JSONObject().apply {
27+
put("cand_font", "") // 使用系统默认
28+
put("font", "") // 使用系统默认
29+
put("preedit_font", "")
30+
put("popup_key_font", "")
31+
put("key_main_font", "")
32+
put("key_alt_font", "")
33+
}
34+
35+
configFile.writeText(testConfig.toString(2))
36+
println("创建测试字体配置: ${configFile.absolutePath}")
37+
}
38+
39+
/**
40+
* 验证字体配置是否正确加载
41+
*/
42+
fun validateFontConfig(): Boolean {
43+
val fontsDir = File(appContext.getExternalFilesDir(null), "fonts")
44+
val configFile = File(fontsDir, "fontset.json")
45+
46+
return try {
47+
if (!configFile.exists()) {
48+
println("字体配置文件不存在")
49+
return false
50+
}
51+
52+
val config = JSONObject(configFile.readText())
53+
val requiredKeys = listOf("cand_font", "font", "preedit_font", "popup_key_font", "key_main_font", "key_alt_font")
54+
55+
requiredKeys.all { key ->
56+
config.has(key).also { hasKey ->
57+
if (!hasKey) println("缺少必需的键: $key")
58+
}
59+
}
60+
} catch (e: Exception) {
61+
println("验证字体配置时出错: ${e.message}")
62+
false
63+
}
64+
}
65+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* SPDX-FileCopyrightText: Copyright 2025 Fcitx5 for Android Contributors
4+
*/
5+
package org.fcitx.fcitx5.android.data.fonts
6+
7+
import android.app.AlertDialog
8+
import android.content.Context
9+
import androidx.annotation.StringRes
10+
import org.fcitx.fcitx5.android.R
11+
import org.fcitx.fcitx5.android.data.prefs.AppPrefs
12+
import org.fcitx.fcitx5.android.input.AutoScaleTextView
13+
import org.fcitx.fcitx5.android.utils.appContext
14+
import org.json.JSONObject
15+
import java.io.File
16+
17+
object FontManager {
18+
19+
private val fontsDir = File(appContext.getExternalFilesDir(null), "fonts")
20+
private val fontConfigFile = File(fontsDir, "fontset.json")
21+
22+
init {
23+
if (!fontsDir.exists()) {
24+
fontsDir.mkdirs()
25+
}
26+
}
27+
28+
/**
29+
* 获取可用字体列表
30+
*/
31+
fun getAvailableFonts(): List<FontInfo> {
32+
val fonts = mutableListOf<FontInfo>()
33+
34+
// 添加系统默认字体
35+
fonts.add(FontInfo("", "System Default"))
36+
37+
// 扫描 fonts 目录
38+
if (fontsDir.exists()) {
39+
fontsDir.listFiles { file ->
40+
file.isFile && (file.extension.lowercase() in listOf("ttf", "otf"))
41+
}?.forEach { file ->
42+
fonts.add(FontInfo(file.name, file.nameWithoutExtension))
43+
}
44+
}
45+
46+
return fonts.sortedBy { it.displayName }
47+
}
48+
49+
/**
50+
* 显示字体选择对话框
51+
*/
52+
fun showFontPickerDialog(
53+
context: Context,
54+
@StringRes titleRes: Int,
55+
onFontSelected: (String) -> Unit
56+
) {
57+
val fonts = getAvailableFonts()
58+
val fontNames = fonts.map { it.displayName }.toTypedArray()
59+
60+
AlertDialog.Builder(context)
61+
.setTitle(titleRes)
62+
.setItems(fontNames) { _, which ->
63+
val selectedFont = fonts[which].fileName
64+
onFontSelected(selectedFont)
65+
}
66+
.setNegativeButton(android.R.string.cancel, null)
67+
.show()
68+
}
69+
70+
/**
71+
* 更新字体配置文件
72+
*/
73+
fun updateFontConfiguration() {
74+
try {
75+
val fontPrefs = AppPrefs.getInstance().fonts
76+
val config = JSONObject().apply {
77+
val candFont = fontPrefs.candFont.getValue()
78+
val preeditFont = fontPrefs.preeditFont.getValue()
79+
val popupKeyFont = fontPrefs.popupKeyFont.getValue()
80+
val keyMainFont = fontPrefs.keyMainFont.getValue()
81+
val keyAltFont = fontPrefs.keyAltFont.getValue()
82+
val defaultFont = fontPrefs.defaultFont.getValue()
83+
84+
put("cand_font", candFont)
85+
put("font", defaultFont)
86+
put("preedit_font", preeditFont)
87+
put("popup_key_font", popupKeyFont)
88+
put("key_main_font", keyMainFont)
89+
put("key_alt_font", keyAltFont)
90+
}
91+
92+
if (!fontsDir.exists()) {
93+
fontsDir.mkdirs()
94+
}
95+
96+
fontConfigFile.writeText(config.toString(2))
97+
98+
// 清除字体缓存,触发重新加载
99+
AutoScaleTextView.clearFontCache()
100+
} catch (e: Exception) {
101+
// 记录错误但不崩溃
102+
e.printStackTrace()
103+
}
104+
}
105+
106+
/**
107+
* 字体信息
108+
*/
109+
data class FontInfo(
110+
val fileName: String,
111+
val displayName: String
112+
)
113+
}

app/src/main/java/org/fcitx/fcitx5/android/data/prefs/AppPrefs.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,35 @@ class AppPrefs(private val sharedPreferences: SharedPreferences) {
359359
)
360360
}
361361

362+
inner class Fonts : ManagedPreferenceCategory(R.string.fonts, sharedPreferences) {
363+
val candFont: ManagedPreference.PString
364+
val preeditFont: ManagedPreference.PString
365+
val popupKeyFont: ManagedPreference.PString
366+
val keyMainFont: ManagedPreference.PString
367+
val keyAltFont: ManagedPreference.PString
368+
val defaultFont: ManagedPreference.PString
369+
370+
init {
371+
candFont = ManagedPreference.PString(sharedPreferences, "cand_font", "").apply { register() }
372+
preeditFont = ManagedPreference.PString(sharedPreferences, "preedit_font", "").apply { register() }
373+
popupKeyFont = ManagedPreference.PString(sharedPreferences, "popup_key_font", "").apply { register() }
374+
keyMainFont = ManagedPreference.PString(sharedPreferences, "key_main_font", "").apply { register() }
375+
keyAltFont = ManagedPreference.PString(sharedPreferences, "key_alt_font", "").apply { register() }
376+
defaultFont = ManagedPreference.PString(sharedPreferences, "font", "").apply { register() }
377+
378+
val onChange = ManagedPreference.OnChangeListener<String> { _, _ ->
379+
org.fcitx.fcitx5.android.data.fonts.FontManager.updateFontConfiguration()
380+
}
381+
382+
candFont.registerOnChangeListener(onChange)
383+
preeditFont.registerOnChangeListener(onChange)
384+
popupKeyFont.registerOnChangeListener(onChange)
385+
keyMainFont.registerOnChangeListener(onChange)
386+
keyAltFont.registerOnChangeListener(onChange)
387+
defaultFont.registerOnChangeListener(onChange)
388+
}
389+
}
390+
362391
private val providers = mutableListOf<ManagedPreferenceProvider>()
363392

364393
fun <T : ManagedPreferenceProvider> registerProvider(
@@ -378,6 +407,7 @@ class AppPrefs(private val sharedPreferences: SharedPreferences) {
378407
val candidates = Candidates().register()
379408
val clipboard = Clipboard().register()
380409
val symbols = Symbols().register()
410+
val fonts = Fonts().register()
381411
val advanced = Advanced().register()
382412

383413
@Keep

0 commit comments

Comments
 (0)