Jetpack Compose中通过Uri读取本地文件加载字体的问题
问题描述
我知道怎么从资源里加载字体,但不想给应用打包太多字体,所以想通过Uri使用本地存储里的字体。拿到文件Uri后,尝试用它作为字体时出现加载错误,相关代码和报错如下:
获取Uri与文件的代码
// use the standard font if null val currentFont = remember { mutableStateOf<String?>(null) } val fontPath = remember { mutableStateOf<String?>(null) } val fontFile = remember { mutableStateOf<File?>(null) } val launcher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> if (uri != null) { fontPath.value = uri.path if (fontPath.value != null) { if (fontPath.value!!.contains("document/raw:")) { fontPath.value = fontPath.value!!.replace("/document/raw:",""); } fontFile.value = File(fontPath.value) currentFont.value = "name_in_future" } } } //In UI ... RegularButton(title = "Button") { launcher.launch(arrayOf("*/*")) } ...
UI代码
CustomTextField( value = framesText[page.value.toInt() - 1], onValueChange = { framesText[page.value.toInt() - 1] = it }, modifier = Modifier .weight(1f) .fillMaxWidth() .padding(horizontal = 5.dp) .border(1.dp, Color.Black), fontSize = project.value.fontSize.sp, // WORK_HERE -> font = if (fontFile.value != null) Font(fontFile.value!!) else Font(R.font.jet_brains_mono), contentAlignment = Alignment.TopStart )
自定义TextField代码
@Composable fun CustomTextField( value: String, onValueChange: (String) -> Unit, modifier: Modifier = Modifier, fontSize: TextUnit = 20.sp, font: Font = Font(R.font.jet_brains_mono), contentAlignment: Alignment = Alignment.CenterStart, keyboardActions: KeyboardActions = KeyboardActions {}, keyboardOptions: KeyboardOptions = KeyboardOptions.Default ) { BasicTextField( value = value, onValueChange = { onValueChange(it) }, modifier = modifier, textStyle = TextStyle( fontSize = fontSize, lineHeight = fontSize, color = MaterialTheme.colorScheme.primary, fontFamily = FontFamily(font) ), cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), keyboardActions = keyboardActions, keyboardOptions = keyboardOptions ) { innerTextField -> Box(modifier = Modifier .background( color = MaterialTheme.colorScheme.onPrimary, shape = RoundedCornerShape(4.dp) ) .padding(4.dp) .fillMaxHeight(), contentAlignment = contentAlignment, content = { innerTextField() } ) } }
报错信息
Error mapping font file /storage/emulated/0/Download/RobotoMono-VariableFont_wght.ttf FATAL EXCEPTION: main java.lang.IllegalStateException: Could not load font //And the same with "/document/raw:": Error mapping font file /document/raw:/storage/emulated/0/Download/RobotoMono-VariableFont_wght.ttf
解决方案
核心问题是ActivityResultContracts.OpenDocument()返回的Uri是内容Uri,不是本地文件路径,直接转File会导致权限失效或路径无效。正确做法是通过ContentResolver获取文件输入流,再加载字体。
步骤1:修改Uri处理逻辑
保留原始Uri,通过输入流加载字体,同时申请永久访问权限避免重启后失效:
val context = LocalContext.current val selectedFontUri = remember { mutableStateOf<Uri?>(null) } val customFontFamily = remember { mutableStateOf<FontFamily?>(null) } val launcher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> if (uri != null) { selectedFontUri.value = uri // 申请永久访问权限 context.contentResolver.takePersistableUriPermission( uri, Intent.FLAG_GRANT_READ_URI_PERMISSION ) // 协程中执行IO操作加载字体 lifecycleScope.launch { runCatching { context.contentResolver.openInputStream(uri)?.use { inputStream -> val font = Font(inputStream) customFontFamily.value = FontFamily(font) } }.onFailure { Log.e("FontLoad", "加载字体失败", it) } } } }
步骤2:更新UI调用代码
直接使用加载好的FontFamily替代原有的Font参数:
CustomTextField( value = framesText[page.value.toInt() - 1], onValueChange = { framesText[page.value.toInt() - 1] = it }, modifier = Modifier .weight(1f) .fillMaxWidth() .padding(horizontal = 5.dp) .border(1.dp, Color.Black), fontSize = project.value.fontSize.sp, fontFamily = customFontFamily.value ?: FontFamily(Font(R.font.jet_brains_mono)), contentAlignment = Alignment.TopStart )
步骤3:调整CustomTextField参数
将font参数替换为fontFamily,更贴合实际使用场景:
@Composable fun CustomTextField( value: String, onValueChange: (String) -> Unit, modifier: Modifier = Modifier, fontSize: TextUnit = 20.sp, fontFamily: FontFamily = FontFamily(Font(R.font.jet_brains_mono)), contentAlignment: Alignment = Alignment.CenterStart, keyboardActions: KeyboardActions = KeyboardActions {}, keyboardOptions: KeyboardOptions = KeyboardOptions.Default ) { BasicTextField( value = value, onValueChange = { onValueChange(it) }, modifier = modifier, textStyle = TextStyle( fontSize = fontSize, lineHeight = fontSize, color = MaterialTheme.colorScheme.primary, fontFamily = fontFamily ), cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), keyboardActions = keyboardActions, keyboardOptions = keyboardOptions ) { innerTextField -> Box(modifier = Modifier .background( color = MaterialTheme.colorScheme.onPrimary, shape = RoundedCornerShape(4.dp) ) .padding(4.dp) .fillMaxHeight(), contentAlignment = contentAlignment, content = { innerTextField() } ) } }
关键说明
OpenDocument返回的Uri属于系统内容提供器,必须通过ContentResolver.openInputStream()访问,不能直接用File类读取。takePersistableUriPermission能让应用在重启后仍保留文件访问权限,否则权限会随进程结束失效。- 字体加载属于IO操作,必须放在协程中执行,避免阻塞主线程引发ANR。
内容的提问来源于stack exchange,提问作者CosmicTea
相关产品推荐
相关产品推荐

