1. 基础概念
- 像素密度(density) dpi
像素密度是屏幕单位面积内的像素数,称为 dpi(每英寸的点数).
- 每英尺的像素点数 ppi (pixels of per inch)
ppi = √(宽^2 + 高^2)/ 屏幕尺寸
dpi不等于ppi,dpi是android中软件定义的,系统可以自定义修改;ppi是屏幕的物理参数,无法修改。
- 度无关像素 (density-independent pixels)
尺寸相同的两个屏幕可能具有不同数量的像素,直接使用相同的像素会导致2个屏幕上的内容看起来大小不一样;但是如果定义2个设备屏幕dp值大小相等,使用相同的dp值则可以让2个屏幕上的内容看起来大小一样。
px = dp * (dpi / 160)
dp 是一个虚拟像素单位,1dp 等于中密度屏幕(160dpi)上的 1px。
2. 基于资源限定符方案
阅读官方文档,了解下各种限定符
假如将手机上的内容不做缩放显示到大屏设备上,视觉可能看起来太小了;
假如按比例缩放后显示到大屏设备上,视觉上就是单纯的放大,不符合大屏设备的UI/UX体验。
因此按比例缩放仅适用于尺寸相近的设备上,如果差异过大,那么缩放后的效果同样非常不好。
理论上设计师应该给出各种尺寸的设计方案/设计稿,但是现实情况往往设计师只做出一套设计,开发人员需要基于这一套设计适配到不同尺寸的屏幕上。
设计/开发一般都是基于宽度做方案。
比如存在以下两个分辨率的屏幕,dpi都是160
-
设备A 2560*1600 sw1600dp w2560dp 宽高比16:10
-
设备B 2560*1440 sw1440dp w2560dp 宽高比16:9
那么此时使用 w2560dp 即可适配绝大部分场景。
假如还有其他尺寸,
-
设备C 3200*2000 dpi=240 sw1333dp w2133dp 宽高比16:10
-
设备B 3840*2160 dpi=120 sw2880dp w5120dp 宽高比16:9
那么此时可以基于A、B设备尺寸等比缩放适配,因为我们使用dp单位,所以可直接根据dp值的比例进行缩放。
设备C 对比 设备A,w2133dp/w2560dp=0.883,缩放了0.883倍,那么可以将资源使用的dp值也缩放0.883倍。
例如:设备A(w2560dp)撑满屏幕宽度应该使用2560dp,设备C(w2133dp)撑满屏幕宽度应该使用2133dp。
插件自动缩放
基于以上思路,这里开发了一套dimens缩放插件 scale-dimens
使用方式:
// pluginManagement 声明使用jitpack仓库
pluginManagement {
repositories {
maven {
url = uri("https://www.jitpack.io/")
}
}
}
// 声明使用插件
plugins {
id("io.github.liu-wanshun.scale-dimens") version "2.0.0-SNAPSHOT"
}
// 在需要缩放的模块下声明配置configPath,比如app模块或者其他lib模块
scaleDimens {
configPath = rootProject.file("scale-demens.yaml")
}
参数说明:
baseQualifier:基于哪个限定符缩放(config下面可以声明一组或多组baseQualifier)
generateQualifier:将要生成的限定符列表(在generateQualifier下面声明内容,可以多个)
qualifier:生成的限定符
scale:缩放比例
以下举例配置
config:
- baseQualifier: "sw1600dp"
generateQualifier:
- qualifier: "w25600dp"
scale: 1f
- qualifier: "w2133dp"
scale: 0.833f
- baseQualifier: "w25600dp"
generateQualifier:
- qualifier: "w25600dp"
scale: 1f
- qualifier: "w2133dp"
scale: 0.833f
配置之后,每次编译时都会根据scale-demens.yaml的配置进行生成缩放文件,缩放生成的文件可以在这个路径下查看
模块/build/generated/res/scaleDimensXXXXX
缩放文件会打包到apk中,无需人工处理。
这样开发人员最少仅需维护一套base的dimens,其他交给插件自动缩放处理即可,对项目代码完全无侵入。
此外需要注意几点:
-
如果app参与分屏,或者旋转屏幕,那么可用宽度(w)可能会变更的,可以考虑其他限定符作为补充
3. 基于修改应用config方案
在你使用的Context(例如activity)在attachBaseContext时注入自定义config; 也可以直接使用自定义的context来加载资源。
override fun attachBaseContext(newBase: Context) {
val newConfig = Configuration().apply {
// 这里设置你想要的config,比如固定屏幕参数/uimode/字体大小/地区语言等等都可以
// screenWidthDp
// smallestScreenWidthDp
// uiMode
// densityDpi
// fontScale
}
val newContext = newBase.createConfigurationContext(newConfig)
super.attachBaseContext(newContext)
}
这样使用这个context关联的resource加载资源时都会使用自定义的config了。
每个需要使用的context都需要特殊处理,存在一定的侵入性。