一、如何使用
-
添加依赖: 在你的
app/build.gradle
文件中加入库的依赖。arduinodependencies { implementation 'me.jessyan:autosize:1.2.1' // 请替换为仓库中的最新版本 }
-
全局配置 (AndroidManifest.xml): 在项目的
AndroidManifest.xml
文件中,给application
节点添加meta-data
配置,告诉库你设计稿的基准尺寸。xml<manifest> <application> ... <!-- 设计图的总宽度 (单位: dp) --> <meta-data android:name="design_width_in_dp" android:value="360"/> <!-- 假设设计稿宽度是360dp (比如720px物理像素在xhdpi下就是360dp) --> <!-- 设计图的总高度 (单位: dp) --> <meta-data android:name="design_height_in_dp" android:value="640"/> <!-- 假设设计稿高度是640dp (比如1280px物理像素在xhdpi下就是640dp) --> ... </application> </manifest>
- 这里的
360dp
和640dp
是关键!它们代表了你的设计稿在某个理想设备(比如 360dp 宽的设备)上显示时的逻辑尺寸。库会根据这个基准去适配其他设备。
- 这里的
-
让 Activity/Fragment 支持适配 (二选一):
- 方法 A (继承 - 简单): 让你需要适配的
Activity
继承库提供的AutoSizeActivity
(或AutoSizeFragmentActivity
等)。你的Fragment
不需要额外操作(只要宿主 Activity 适配了)。 - 方法 B (接口 - 更灵活): 让你需要适配的
Activity
实现IAutoSize
接口,并在onCreate
中调用AutoSize.autoConvertDensity(...)
。这适用于不能改基类的情况。
- 方法 A (继承 - 简单): 让你需要适配的
-
在布局中使用
dp
或sp
: 在你的 XML 布局文件中,所有需要按屏幕缩放的尺寸(宽度、高度、边距、内边距、字体大小等),继续使用dp
或sp
单位。库会在运行时动态修改这些单位的实际计算值。 -
高级配置 (可选):
- 适配策略: 可以设置是只按宽度适配、只按高度适配、还是宽高都考虑 (
AutoSizeConfig.init(this).setBaseOnWidth(true/false)
)。通常只按宽度适配 (setBaseOnWidth(true)
) 是最常用和稳定的,能保证所有屏幕宽度方向上的比例一致。 - 排除适配: 某些特殊页面(如闪屏页、视频播放页)可能不需要适配,可以通过配置排除掉。
- Fragment 自定义适配参数: 如果某个 Fragment 需要不同于 Activity 的适配基准,可以单独设置。
- 适配策略: 可以设置是只按宽度适配、只按高度适配、还是宽高都考虑 (
二、dp
的局限性
虽然 dp
在相同屏幕比例的设备上表现良好,但在以下场景会出问题:
-
屏幕宽高比差异大
-
问题 :
dp
只考虑屏幕密度 ,不考虑物理尺寸。 -
例如:
- 手机 A:5 英寸 720x1280(
density=2.0
),屏幕宽度360dp
。 - 手机 B:6.5 英寸 1080x1920(
density=3.0
),屏幕宽度360dp
。 - 平板 C:10 英寸 1200x1920(
density=2.0
),屏幕宽度600dp
。
- 手机 A:5 英寸 720x1280(
-
现象:
- 在手机 A 和 B 上,
100dp
的按钮物理尺寸接近 (因为dp
已考虑密度)。 - 但在平板 C 上,
100dp
的按钮物理尺寸会更大 (因为屏幕更宽,dp
没有动态调整)。 - 如果设计稿基于手机(如 360dp 宽度),在平板上 UI 可能显得稀疏 或留白过多。
- 在手机 A 和 B 上,
-
-
小屏高分辨率 vs. 大屏低分辨率
-
示例:
-
手机 A:5 英寸 1080x1920(
density=3.0
),屏幕宽度360dp
。 -
手机 B:6 英寸 720x1280(
density=2.0
),屏幕宽度360dp
。 -
现象:
-
虽然屏幕宽度都是
360dp
,但:- 手机 A 的像素更密集(
density=3.0
),100dp
的按钮实际物理尺寸更小。 - 手机 B 的像素更稀疏(
density=2.0
),100dp
的按钮实际物理尺寸更大。
- 手机 A 的像素更密集(
-
用户会感觉手机 A 的 UI "太小" ,手机 B 的 UI "太大" 。
-
-
横竖屏切换时比例失调
- 竖屏时
360dp
占满宽度,横屏时360dp
可能只占一半(因为屏幕宽度变成高度)。 - 如果布局没有针对横屏优化,
dp
无法自动调整比例。
- 竖屏时
-
折叠屏/平板等特殊设备
- 折叠屏展开后,屏幕尺寸变化,但
dp
不会动态调整,可能导致 UI 拉伸或压缩。
- 折叠屏展开后,屏幕尺寸变化,但
二、原理
想象一下,Android 系统有一把"尺子"叫 density
(密度)。这把尺子决定了 1dp
等于屏幕上的多少个物理像素 (px
)。公式是:px = dp * density
。
- 在 1080x1920 (xhdpi, density=2.0) 的设备上,
1dp = 2px
。 - 在 720x1280 (hdpi, density=1.5) 的设备上,
1dp = 1.5px
。
AndroidAutoSize 的魔法就在于:它偷偷修改了系统这把"尺子" (density
) 的刻度!
-
目标: 让任何设备屏幕的宽度(或高度) 都"看起来"和你在
AndroidManifest.xml
中设置的design_width_in_dp
(比如 360dp) 一样宽。 -
计算新尺子:
- 当 App 启动或屏幕旋转时,库会获取当前设备的屏幕实际物理宽度(单位:px) 。
- 库知道你期望的"逻辑宽度"是
design_width_in_dp
(比如 360dp)。 - 它计算出新的 density :
newDensity = 屏幕物理宽度(px) / design_width_in_dp
。 - 例如:一个设备物理宽度是 1080px。期望逻辑宽度是 360dp。那么
newDensity = 1080px / 360dp = 3.0
。
-
偷换尺子:
- 库拿到计算出的
newDensity
。 - 它找到当前
Application
或Activity
的Resources
对象内部的DisplayMetrics
。 - 它把系统原来的
density
、densityDpi
、scaledDensity
(这个和字体缩放有关) 都替换成基于newDensity
计算出来的新值。
- 库拿到计算出的
-
效果:
-
系统现在认为
1dp = newDensity px
(比如 3px)。 -
根据公式
px = dp * newDensity
,如果你在布局里写了一个宽度是180dp
的按钮:- 在期望逻辑宽度为 360dp 的设计稿上,这个按钮应该占屏幕宽度的一半 (180dp / 360dp = 0.5)。
- 在刚才那个 1080px 宽的设备上,
180dp * 3.0 = 540px
。而 540px 正好是 1080px 的一半!
-
这样,无论设备实际宽度是多少,这个按钮在屏幕上看起来都占了一半的宽度,完美等比例缩放。文字大小 (
sp
) 的缩放原理类似(scaledDensity
会跟着density
按比例调整)。
-
-
示例对比
设备 | 物理宽度(px) | 默认 density |
默认宽度(dp) | AndroidAutoSize 调整后 |
---|---|---|---|---|
5" 720x1280(xhdpi) | 720px | 2.0 | 360dp | newDensity = 720/360 = 2.0 (不变) |
6.5" 1080x1920(xxhdpi) | 1080px | 3.0 | 360dp | newDensity = 1080/360 = 3.0 (不变) |
10" 1200x1920(xhdpi) | 1200px | 2.0 | 600dp | newDensity = 1200/360 ≈ 3.33 (动态调整) |
简单比喻: 设计师在 360 格宽的网格纸上画图。AndroidAutoSize 让任何大小的屏幕都"假装"自己是 360 格宽,然后按比例拉伸或压缩每一格的大小,最终把设计图"贴"满整个屏幕,保证了比例。
三、源码调用流程
-
初始化 (通常通过 ContentProvider 自动完成):
- 库包含一个
InitProvider
(继承自ContentProvider
)。 - 当 App 启动时,系统会自动初始化所有
ContentProvider
。 - 在
InitProvider.onCreate()
中,它调用了AutoSize.initCompatMultiProcess(context)
。这是库初始化的入口点,确保在多进程环境下也能工作。
- 库包含一个
-
配置读取与初始化:
-
AutoSize.initCompatMultiProcess()
最终会调用AutoSizeConfig.init()
。 -
AutoSizeConfig
是一个单例类,保存了所有全局配置。 -
在
init()
方法里:- 读取你在
AndroidManifest.xml
中设置的design_width_in_dp
和design_height_in_dp
,保存到配置中。 - 获取应用默认的
DisplayMetrics
。 - 注册 Activity 生命周期监听器 (
Application.ActivityLifecycleCallbacks
) :这是核心钩子!库通过这个监听器知道每个 Activity 何时创建、销毁、旋转。
- 读取你在
-
-
Activity 创建时的适配 (核心):
-
当有 Activity 被创建 (
onActivityCreated
) 时,注册的生命周期回调会被触发。 -
回调中会检查这个 Activity 是否被排除适配(根据配置或是否继承自
AutoSizeActivity
/实现了IAutoSize
)。如果排除,就跳过。 -
关键调用:
AutoSize.autoConvertDensityOfGlobal(activity)
或类似方法。-
这个方法内部会根据你的配置(是按宽还是按高)计算目标尺寸 (
targetDp
):- 如果配置为按宽度适配 (
baseOnWidth = true
),targetDp = design_width_in_dp
。 - 如果按高度适配,
targetDp = design_height_in_dp
。
- 如果配置为按宽度适配 (
-
获取该 Activity 当前可用屏幕区域的物理尺寸(px) (考虑状态栏、导航栏等)。
-
计算新的
density
:newDensity = 屏幕物理尺寸(px) / targetDp
。 -
计算新的
scaledDensity
:通常是newDensity * (系统原始 scaledDensity / 系统原始 density)
,以保持字体缩放比例。 -
计算新的
densityDpi
:newDensityDpi = (int)(newDensity * 160)
。 -
修改资源: 获取该 Activity 的
Resources
对象和它的DisplayMetrics
以及Configuration
。将计算出的newDensity
,newScaledDensity
,newDensityDpi
设置回去。 -
修改 Application 资源 (可选但通常做): 同时也会修改 Application 级别的
DisplayMetrics
,确保后续创建的 View 和某些系统行为也使用新密度。这就是为什么有时你在Application
初始化时获取的屏幕宽高可能不准的原因(已经被库修改了)。
-
-
这样,这个 Activity 及其内部的 View 在布局和绘制时,使用的就是新计算出来的"尺子"了。
-
-
屏幕旋转/配置变化:
- 当屏幕旋转、字体大小改变等导致配置 (
Configuration
) 变化时,Activity 会重建。 - 重建过程又会走到
onActivityCreated
的生命周期回调。 - 库会再次检测并执行步骤 3 的适配计算,根据新的屏幕方向或尺寸重新计算并设置新的
density
等值。
- 当屏幕旋转、字体大小改变等导致配置 (
-
Fragment 适配:
- 如果宿主 Activity 已经适配,Fragment 默认会使用 Activity 适配后的环境,里面的
dp/sp
自然生效。 - 如果需要为某个 Fragment 单独设置不同的设计尺寸 (
design_width_in_dp
/design_height_in_dp
),库提供了在 FragmentonCreate
中调用AutoSize.autoConvertDensity(...)
的方法,原理和 Activity 类似,但作用域仅限于该 Fragment 的视图层次。
- 如果宿主 Activity 已经适配,Fragment 默认会使用 Activity 适配后的环境,里面的
总结与优缺点
-
优点:
- 使用简单: 配置少,布局文件几乎不用改(继续用
dp/sp
)。 - 效果直观: 能很好地实现 UI 的等比例缩放,在各种尺寸设备上视觉比例一致。
- 侵入性相对较低: 主要通过修改
Application
/Activity
的Resources
实现,不需要大量自定义 View。
- 使用简单: 配置少,布局文件几乎不用改(继续用
-
缺点/注意事项:
- 系统级修改: 修改
DisplayMetrics
是全局性的,可能影响某些依赖原始屏幕密度的第三方库或系统组件(如WebView
、地图 SDK 的控件),导致它们显示异常。需要仔细测试或排除特定页面。 - 横竖屏切换: 需要正确处理,库默认处理了重建。
- 设计基准选择:
design_width_in_dp
的选择很重要,选得太小可能导致在大屏上元素过大,选得太大可能导致在小屏上元素过小。360dp 是常见的选择(对应 720x1280 的 xhdpi 设备宽度)。 - 精度: 计算涉及到除法,可能会有极小的舍入误差,通常肉眼不可见。
- 理解成本: 需要理解其修改系统密度的原理,否则在排查某些兼容性问题时可能困惑。
- 系统级修改: 修改
-
使用AndroidAutoSize的区别
对比维度 | 仅用 dp |
使用 AndroidAutoSize |
---|---|---|
适配原理 | 固定 density ,依赖系统计算。 |
动态调整 density ,强制逻辑宽度一致。 |
小屏高分辨率 | UI 可能过小。 | 按比例放大,保持视觉一致。 |
大屏低分辨率 | UI 可能过大。 | 按比例缩小,保持视觉一致。 |
平板/折叠屏 | 留白多或比例失调。 | 自动填充分辨率,比例协调。 |
横竖屏切换 | 可能比例错乱。 | 动态重新计算,保持一致。 |
兼容性 | 无额外依赖。 | 需测试第三方库兼容性(如地图、WebView)。 |