详解 AndroidAutoSize 开源库

一、如何使用

  1. 添加依赖: 在你的 app/build.gradle 文件中加入库的依赖。

    arduino 复制代码
    dependencies {
        implementation 'me.jessyan:autosize:1.2.1' // 请替换为仓库中的最新版本
    }
  2. 全局配置 (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>
    • 这里的 360dp640dp关键!它们代表了你的设计稿在某个理想设备(比如 360dp 宽的设备)上显示时的逻辑尺寸。库会根据这个基准去适配其他设备。
  3. 让 Activity/Fragment 支持适配 (二选一):

    • 方法 A (继承 - 简单): 让你需要适配的 Activity 继承库提供的 AutoSizeActivity(或 AutoSizeFragmentActivity 等)。你的 Fragment 不需要额外操作(只要宿主 Activity 适配了)。
    • 方法 B (接口 - 更灵活): 让你需要适配的 Activity 实现 IAutoSize 接口,并在 onCreate 中调用 AutoSize.autoConvertDensity(...)。这适用于不能改基类的情况。
  4. 在布局中使用 dpsp 在你的 XML 布局文件中,所有需要按屏幕缩放的尺寸(宽度、高度、边距、内边距、字体大小等),继续使用 dpsp 单位。库会在运行时动态修改这些单位的实际计算值。

  5. 高级配置 (可选):

    • 适配策略: 可以设置是只按宽度适配、只按高度适配、还是宽高都考虑 (AutoSizeConfig.init(this).setBaseOnWidth(true/false))。通常只按宽度适配 (setBaseOnWidth(true)) 是最常用和稳定的,能保证所有屏幕宽度方向上的比例一致。
    • 排除适配: 某些特殊页面(如闪屏页、视频播放页)可能不需要适配,可以通过配置排除掉。
    • Fragment 自定义适配参数: 如果某个 Fragment 需要不同于 Activity 的适配基准,可以单独设置。

二、dp 的局限性

虽然 dp相同屏幕比例的设备上表现良好,但在以下场景会出问题:

  1. 屏幕宽高比差异大

    • 问题dp 只考虑屏幕密度 ,不考虑物理尺寸

    • 例如:

      • 手机 A:5 英寸 720x1280(density=2.0),屏幕宽度 360dp
      • 手机 B:6.5 英寸 1080x1920(density=3.0),屏幕宽度 360dp
      • 平板 C:10 英寸 1200x1920(density=2.0),屏幕宽度 600dp
    • 现象

      • 在手机 A 和 B 上,100dp 的按钮物理尺寸接近 (因为 dp 已考虑密度)。
      • 但在平板 C 上,100dp 的按钮物理尺寸会更大 (因为屏幕更宽,dp 没有动态调整)。
      • 如果设计稿基于手机(如 360dp 宽度),在平板上 UI 可能显得稀疏留白过多
  2. 小屏高分辨率 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 的 UI "太小" ,手机 B 的 UI "太大"

  3. 横竖屏切换时比例失调

    • 竖屏时 360dp 占满宽度,横屏时 360dp 可能只占一半(因为屏幕宽度变成高度)。
    • 如果布局没有针对横屏优化,dp 无法自动调整比例。
  4. 折叠屏/平板等特殊设备

    • 折叠屏展开后,屏幕尺寸变化,但 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) 的刻度!

  1. 目标: 让任何设备屏幕的宽度(或高度) 都"看起来"和你在 AndroidManifest.xml 中设置的 design_width_in_dp (比如 360dp) 一样宽。

  2. 计算新尺子:

    • 当 App 启动或屏幕旋转时,库会获取当前设备的屏幕实际物理宽度(单位:px)
    • 库知道你期望的"逻辑宽度"是 design_width_in_dp (比如 360dp)。
    • 它计算出新的 densitynewDensity = 屏幕物理宽度(px) / design_width_in_dp
    • 例如:一个设备物理宽度是 1080px。期望逻辑宽度是 360dp。那么 newDensity = 1080px / 360dp = 3.0
  3. 偷换尺子:

    • 库拿到计算出的 newDensity
    • 它找到当前 ApplicationActivityResources 对象内部的 DisplayMetrics
    • 它把系统原来的 densitydensityDpiscaledDensity (这个和字体缩放有关) 都替换成基于 newDensity 计算出来的新值。
  4. 效果:

    • 系统现在认为 1dp = newDensity px (比如 3px)。

    • 根据公式 px = dp * newDensity,如果你在布局里写了一个宽度是 180dp 的按钮:

      • 在期望逻辑宽度为 360dp 的设计稿上,这个按钮应该占屏幕宽度的一半 (180dp / 360dp = 0.5)。
      • 在刚才那个 1080px 宽的设备上,180dp * 3.0 = 540px。而 540px 正好是 1080px 的一半!
    • 这样,无论设备实际宽度是多少,这个按钮在屏幕上看起来都占了一半的宽度,完美等比例缩放。文字大小 (sp) 的缩放原理类似(scaledDensity 会跟着 density 按比例调整)。

  5. 示例对比

设备 物理宽度(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 格宽,然后按比例拉伸或压缩每一格的大小,最终把设计图"贴"满整个屏幕,保证了比例。


三、源码调用流程

  1. 初始化 (通常通过 ContentProvider 自动完成):

    • 库包含一个 InitProvider (继承自 ContentProvider)。
    • 当 App 启动时,系统会自动初始化所有 ContentProvider
    • InitProvider.onCreate() 中,它调用了 AutoSize.initCompatMultiProcess(context)。这是库初始化的入口点,确保在多进程环境下也能工作。
  2. 配置读取与初始化:

    • AutoSize.initCompatMultiProcess() 最终会调用 AutoSizeConfig.init()

    • AutoSizeConfig 是一个单例类,保存了所有全局配置。

    • init() 方法里:

      • 读取你在 AndroidManifest.xml 中设置的 design_width_in_dpdesign_height_in_dp,保存到配置中。
      • 获取应用默认的 DisplayMetrics
      • 注册 Activity 生命周期监听器 (Application.ActivityLifecycleCallbacks) :这是核心钩子!库通过这个监听器知道每个 Activity 何时创建、销毁、旋转。
  3. Activity 创建时的适配 (核心):

    • 当有 Activity 被创建 (onActivityCreated) 时,注册的生命周期回调会被触发。

    • 回调中会检查这个 Activity 是否被排除适配(根据配置或是否继承自 AutoSizeActivity/实现了 IAutoSize)。如果排除,就跳过。

    • 关键调用:AutoSize.autoConvertDensityOfGlobal(activity) 或类似方法。

      • 这个方法内部会根据你的配置(是按宽还是按高)计算目标尺寸 (targetDp):

        • 如果配置为按宽度适配 (baseOnWidth = true),targetDp = design_width_in_dp
        • 如果按高度适配,targetDp = design_height_in_dp
      • 获取该 Activity 当前可用屏幕区域的物理尺寸(px) (考虑状态栏、导航栏等)。

      • 计算新的 densitynewDensity = 屏幕物理尺寸(px) / targetDp

      • 计算新的 scaledDensity:通常是 newDensity * (系统原始 scaledDensity / 系统原始 density),以保持字体缩放比例。

      • 计算新的 densityDpinewDensityDpi = (int)(newDensity * 160)

      • 修改资源: 获取该 Activity 的 Resources 对象和它的 DisplayMetrics 以及 Configuration。将计算出的 newDensity, newScaledDensity, newDensityDpi 设置回去

      • 修改 Application 资源 (可选但通常做): 同时也会修改 Application 级别的 DisplayMetrics,确保后续创建的 View 和某些系统行为也使用新密度。这就是为什么有时你在 Application 初始化时获取的屏幕宽高可能不准的原因(已经被库修改了)。

    • 这样,这个 Activity 及其内部的 View 在布局和绘制时,使用的就是新计算出来的"尺子"了。

  4. 屏幕旋转/配置变化:

    • 当屏幕旋转、字体大小改变等导致配置 (Configuration) 变化时,Activity 会重建。
    • 重建过程又会走到 onActivityCreated 的生命周期回调。
    • 库会再次检测并执行步骤 3 的适配计算,根据新的屏幕方向或尺寸重新计算并设置新的 density 等值。
  5. Fragment 适配:

    • 如果宿主 Activity 已经适配,Fragment 默认会使用 Activity 适配后的环境,里面的 dp/sp 自然生效。
    • 如果需要为某个 Fragment 单独设置不同的设计尺寸 (design_width_in_dp / design_height_in_dp),库提供了在 Fragment onCreate 中调用 AutoSize.autoConvertDensity(...) 的方法,原理和 Activity 类似,但作用域仅限于该 Fragment 的视图层次。

总结与优缺点

  • 优点:

    • 使用简单: 配置少,布局文件几乎不用改(继续用 dp/sp)。
    • 效果直观: 能很好地实现 UI 的等比例缩放,在各种尺寸设备上视觉比例一致。
    • 侵入性相对较低: 主要通过修改 Application/ActivityResources 实现,不需要大量自定义 View。
  • 缺点/注意事项:

    • 系统级修改: 修改 DisplayMetrics 是全局性的,可能影响某些依赖原始屏幕密度的第三方库或系统组件(如 WebView、地图 SDK 的控件),导致它们显示异常。需要仔细测试或排除特定页面。
    • 横竖屏切换: 需要正确处理,库默认处理了重建。
    • 设计基准选择: design_width_in_dp 的选择很重要,选得太小可能导致在大屏上元素过大,选得太大可能导致在小屏上元素过小。360dp 是常见的选择(对应 720x1280 的 xhdpi 设备宽度)。
    • 精度: 计算涉及到除法,可能会有极小的舍入误差,通常肉眼不可见。
    • 理解成本: 需要理解其修改系统密度的原理,否则在排查某些兼容性问题时可能困惑。
  • 使用AndroidAutoSize的区别

对比维度 仅用 dp 使用 AndroidAutoSize
适配原理 固定 density,依赖系统计算。 动态调整 density,强制逻辑宽度一致。
小屏高分辨率 UI 可能过小。 按比例放大,保持视觉一致。
大屏低分辨率 UI 可能过大。 按比例缩小,保持视觉一致。
平板/折叠屏 留白多或比例失调。 自动填充分辨率,比例协调。
横竖屏切换 可能比例错乱。 动态重新计算,保持一致。
兼容性 无额外依赖。 需测试第三方库兼容性(如地图、WebView)。
相关推荐
哒哒哒5285202 分钟前
HTTP缓存
前端·面试
T___4 分钟前
从入门到放弃?带你重新认识 Headless UI
前端·设计模式
wordbaby6 分钟前
React Router 中调用 Actions 的三种方式详解
前端·react.js
黄丽萍12 分钟前
前端Vue3项目代码开发规范
前端
curdcv_po15 分钟前
🏄公司报销,培养我成一名 WebGL 工程师⛵️
前端
Jolyne_26 分钟前
前端常用的树处理方法总结
前端·算法·面试
wordbaby28 分钟前
后端的力量,前端的体验:React Router Server Action 的魔力
前端·react.js
Alang29 分钟前
Mac Mini M4 16G 内存本地大模型性能横评:9 款模型实测对比
前端·llm·aigc
林太白29 分钟前
Rust-连接数据库
前端·后端·rust
wordbaby37 分钟前
让数据“流动”起来:React Router Client Action 与组件的无缝协作
前端·react.js