以安卓开发者的视角,资源有很多种类,不过常用的是这几种
而KMP中的UI一般用Compose
其中的anim,layout,colors,themes都使用代码的形式实现
而KMP中目前貌似没有通用的字符串和图片资源管理和获取的方式,于是我们自己实现一下
字符串的实现我的实现很简单,就是直接用一个单例类来记录各种字符串资源
大概实现是这样:
/**
* creator: lt
* effect : 字符串资源
* warning:
*/
object Strings {
val separator: String
//单词间的分隔符
get() {
return when (LanguageManager.currLanguage) {
EN -> " "
ZH -> ""
}
}
val respose_error: String
get() {
return when (LanguageManager.currLanguage) {
EN -> "Server error, please try again later!"
ZH -> "服务器错误,请稍后再试!"
}
}
}
其中 LanguageManager.currLanguage 是一个自定义的state,所以文本是可以响应式的根据语言改变app内的文本的(ps:可以这么做,不过目前我是用的是隐式参数的方式)
使用方式为:
Text(Strings.respose_error)
简单方便
图片资源我们也可以像字符串资源一样使用代码的形式来实现
实现方式:
object Painters {
val select: Painter
@Composable
get() = PainterGenerator.generate("select", "")
val back: Painter
@Composable
get() {
return when (LanguageManager.currLanguage) {
EN -> PainterGenerator.generate("back", "")
ZH -> PainterGenerator.generate("back", "zh")
}
}
}
select是单语言时的形式,back是有多种语言时的形式,同样可以响应式的根据语言自动切换图片
使用方式:
Image(Painters.back,null)
同样很简单
然后我们看一下 PainterGenerator.generate 是怎么实现的
common:
/**
* creator: lt
* effect : Painter生成器,用于生成静态的图片资源
* warning:
*/
expect object PainterGenerator {
/**
* 生成Painter
* drawable-zh-xxhdpi/ic_launcher.webp
* [imageName]文件名: ic_launcher
* [languageName]资源的语言: zh 如果zh目录中没有,则需传空字符串(要保证传的资源的语言的目录中要有这个图片才行)
*/
@Composable
fun generate(
imageName: String,
languageName: String,
): Painter
}
android:
actual object PainterGenerator {
private var currLocale =
private val androidResource =
Resources(
app.assets,
app.resources.displayMetrics,
Configuration(app.resources.configuration)
)
/**
* 生成Painter
* drawable-zh-xxhdpi/ic_launcher.webp
* [imageName]文件名: ic_launcher
* [languageName]资源的语言: zh
*/
@Composable
actual fun generate(
imageName: String,
languageName: String
): Painter = remember(imageName, languageName) {
//如果没有指定语言,就是用默认语言的图片,如果指定了语言,就设置一下资源的语言
if (languageName.isEmpty())
androidResource.configuration.setLocale(currLocale)
else
androidResource.configuration.setLocale(Locale(languageName))
//获取drawable的id
val id = ExceptionUtil.releaseCatchThrowable({
androidResource.getIdentifier(
imageName, "drawable", app.packageName
)
}) ?: R.drawable.load_error
//根据id,使用带有语言设置的资源来加载
BitmapPainter(
androidResource.getDrawable(id).asT().bitmap.asImageBitmap()
)
}
}
安卓的实现复杂一些,其实就是通过 resource 对象的 getIdentifier 方法,然后设置相应的语言并通过文件夹名和文件名来找到相应的资源id,并加载,然后将bitmap转换为相应资源
ps:使用此方式安卓不能混淆资源名和混淆移除无用的resource文件
desktop 和 iOS:
actual object PainterGenerator {
/**
* 生成Painter
* drawable-zh-xxhdpi/ic_launcher.webp
* [imageName]文件名: ic_launcher
* [languageName]资源的语言: zh
*/
@Composable
actual fun generate(
imageName: String,
languageName: String
): Painter = painterResource(remember(imageName, languageName) {
"drawable%s-xxhdpi/%s.webp".format(
if (languageName.isEmpty()) "" else "-$languageName",
imageName
)
})
}
desktop和iOS没啥说的,其实就是用了自带的api,内部是通过classLoder(desktop)加载的资源
js:
actual object PainterGenerator {
/**
* 生成Painter
* drawable-zh-xxhdpi/ic_launcher.webp
* [imageName]文件名: ic_launcher
* [languageName]资源的语言: zh
*/
@OptIn(ExperimentalResourceApi::class)
@Composable
actual fun generate(
imageName: String,
languageName: String
): Painter {
val state: MutableState> =
remember(imageName, languageName) { mutableStateOf(LoadState.Loading()) }
LaunchedSafeEffect(imageName, languageName) {
state.value = try {
LoadState.Success(
resource(
"drawable%s-xxhdpi/%s.webp".format(
if (languageName.isEmpty()) "" else "-$languageName",
imageName
)
).readBytes()
.toImageBitmap()
)
} catch (e: Exception) {
e.w()
LoadState.Error(e)
}
}
return BitmapPainter(state.value.orEmpty())
}
}
js的实现也是使用了自带的api,不过是异步的,因为内部是走的网络请求加载的资源
然后我们可以通过配置资源目录,将每个端的资源目录指向一个目录
这样就实现了图片资源管理的功能
ps:其实图片资源的类我们完全可以通过ksp去生成,不过这不是这篇文章的重点,感兴趣的同学可以自己动手试试,参考:使用KSP处理注解和生成Kotlin代码
end