Android开发神器:AndroidAutoSize,轻松搞定屏幕适配

13 阅读20分钟

Android开发神器:AndroidAutoSize,轻松搞定屏幕适配

屏幕适配之痛,你中招了吗?

作为一名 Android 开发者,相信大家都有过被屏幕适配折磨的经历。在这个 Android 设备碎片化严重的时代,不同品牌、不同型号的手机,屏幕尺寸、分辨率、像素密度千差万别。

曾经在我开发一个项目时,在设计图上看着完美的布局,在不同的测试设备上一跑,简直是 “惨不忍睹”。有的手机上,按钮变得巨大,文字被拉伸得变形;有的手机上,界面元素挤作一团,留白过多,完全失去了原本的美感和可用性。为了解决这些问题,我尝试过各种方法,创建不同的布局文件适配不同的屏幕尺寸,使用各种复杂的单位换算公式,但结果往往是顾此失彼,解决了一个问题,又冒出新的问题。

相信很多开发者都和我一样,在屏幕适配的道路上 “踩过无数的坑”,花费大量的时间和精力,却依然难以达到理想的效果。那么,有没有一种简单高效的解决方案呢?答案就是今天要给大家介绍的 AndroidAutoSize 开源库。

认识 AndroidAutoSize:屏幕适配救星登场

AndroidAutoSize 是一个基于今日头条屏幕适配方案的开源库,它就像是一位贴心的助手,能帮助我们轻松解决屏幕适配的难题。这个库的核心原理并不复杂,简单来说,它通过动态计算设备的屏幕尺寸,然后自动调整应用的布局参数,使得应用在不同尺寸的设备上都能呈现出相同的用户界面效果 。

在 Android 开发中,我们知道,不同设备的屏幕尺寸、分辨率和像素密度各不相同,这就导致了同样的布局在不同设备上显示效果可能大相径庭。而 AndroidAutoSize 通过巧妙地修改系统的一些关键参数,比如 DisplayMetrics 中的 density、densityDpi 和 scaledDensity 等,来实现对不同屏幕的适配。

举个例子,假设我们的设计稿是基于 360dp 宽度的设备来设计的,在使用 AndroidAutoSize 后,无论设备的实际宽度是多少,库都会自动计算出一个合适的缩放比例,然后将布局中的所有尺寸按照这个比例进行缩放。这样,在不同宽度的设备上,布局元素的相对大小和位置就能保持一致,从而实现了屏幕适配。

AndroidAutoSize 的出现,就像是为我们在黑暗中点亮了一盏明灯,让我们不再为屏幕适配的问题而烦恼。它不仅大大提高了开发效率,还能让我们的应用在各种设备上都能展现出完美的用户体验。接下来,我们就一起来看看如何在项目中使用这个强大的开源库吧。

从入门到精通:使用指南

(一)引入依赖

使用 AndroidAutoSize,首先要在项目中引入依赖。打开项目的app/build\.gradle文件,在dependencies闭包中添加如下依赖:

dependencies {
    implementation 'me.jessyan:autosize:1.2.1' // 请替换为仓库中的最新版本
}

添加完依赖后,记得点击 Sync Project 按钮,让 Gradle 同步项目,下载并添加依赖到项目中 。

(二)设计图尺寸配置

引入依赖后,要告诉 AndroidAutoSize 你的设计图尺寸。在项目的AndroidManifest\.xml文件中,给application节点添加meta \- data配置:

<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是设计稿的宽和高,需根据实际设计稿尺寸填写。比如设计稿宽度是 720px,在 xhdpi(密度为 2.0)的设备上,对应的 dp 值就是720px / 2\.0 = 360dp

(三)Activity 适配

在完成上述配置后,Activity 默认支持屏幕适配。也就是说,无需额外操作,Activity 中的布局会按照设定的设计图尺寸进行适配。

不过,在某些特殊情况下,我们可能需要对 Activity 的适配进行特殊处理。比如,某个 Activity 不想进行适配,这时只需让该 Activity 实现CancelAdapt接口即可:

public class CancelAdaptActivity extends AppCompatActivity implements CancelAdapt {
    // 该Activity将不会进行适配
}

如果某个 Activity 想自定义宽高适配参数,不想使用全局定义好的参数,可让该 Activity 实现CustomAdapt接口,并实现对应的方法:

public class CustomAdaptActivity extends AppCompatActivity implements CustomAdapt {
    /**
     * 是否按照宽度进行等比例适配 (为了保证在高宽比不同的屏幕上也能正常适配, 所以只能在宽度和高度之中选择一个作为基准进行适配)
     *
     * @return {@code true} 为按照宽度进行适配, {@code false} 为按照高度进行适配
     */
    @Override
    public boolean isBaseOnWidth() {
        return false;
    }

    /**
     * 返回设计图上的设计尺寸, 单位dp
     * {@link #getSizeInDp} 须配合 {@link #isBaseOnWidth()} 使用, 规则如下:
     * 如果 {@link #isBaseOnWidth()} 返回 {@code true}, {@link #getSizeInDp} 则应该返回设计图的总宽度
     * 如果 {@link #isBaseOnWidth()} 返回 {@code false}, {@link #getSizeInDp} 则应该返回设计图的总高度
     * 如果您不需要自定义设计图上的设计尺寸, 想继续使用在 AndroidManifest 中填写的设计图尺寸, {@link #getSizeInDp} 则返回 {@code 0}
     *
     * @return 设计图上的设计尺寸, 单位dp
     */
    @Override
    public float getSizeInDp() {
        return 667;
    }
}

在上述代码中,isBaseOnWidth方法用于指定适配基准,getSizeInDp方法用于返回设计图的尺寸。

(四)Fragment 适配

Fragment 的适配与 Activity 类似,但在使用前,需要先在Application初始化时开启对 Fragment 的支持:

public class YourApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        AutoSizeConfig.getInstance().setCustomFragment(true);
    }
}

开启支持后,若某个 Fragment 的设计图尺寸与在AndroidManifest中填写的全局设计图尺寸不同,可让该 Fragment 实现CustomAdapt接口来扩展适配参数:

public class CustomAdaptFragment extends Fragment implements CustomAdapt {
    @Override
    public boolean isBaseOnWidth() {
        return false;
    }

    @Override
    public float getSizeInDp() {
        return 667;
    }
}

若某个 Fragment 想放弃适配,实现CancelAdapt接口即可:

public class CancelAdaptFragment extends Fragment implements CancelAdapt {
    // 该Fragment将不会进行适配
}

(五)高级配置

除了上述基本配置外,AndroidAutoSize 还提供了一些高级配置选项,以满足更复杂的适配需求。

适配策略选择:可以通过AutoSizeConfig来设置适配策略,比如只按宽度适配、只按高度适配或者宽高都考虑。通常只按宽度适配(setBaseOnWidth\(true\))是最常用和稳定的,能保证所有屏幕宽度方向上的比例一致。示例代码如下:

AutoSizeConfig.getInstance().init(this).setBaseOnWidth(true); // 只按宽度适配

如果希望按高度适配,可以将参数设置为false

AutoSizeConfig.getInstance().init(this).setBaseOnWidth(false); // 只按高度适配

排除特定页面适配:某些特殊页面,如闪屏页、视频播放页,可能不需要适配,可以通过配置排除掉。在AndroidManifest\.xml中,为不需要适配的 Activity 添加tools:replace=\&\#34;android:configChanges\&\#34;属性,并将android:configChanges设置为screenSize\|smallestScreenSize\|screenLayout\|orientation\|keyboardHidden\|navigation ,示例如下:

<activity
    android:name=".SplashActivity"
    android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden|navigation"
    tools:replace="android:configChanges">
</activity>

特定 Fragment 单独设置适配基准:对于特定的 Fragment,可以单独设置适配基准。在 Fragment 中实现CustomAdapt接口,并在isBaseOnWidth方法中返回truefalse来指定适配基准,在getSizeInDp方法中返回设计图的尺寸,如前文 Fragment 适配部分的示例代码所示 。

通过这些高级配置选项,我们可以更加灵活地应对各种复杂的屏幕适配场景,让应用在不同设备上都能呈现出完美的用户界面。

原理大揭秘:它是如何工作的?

了解了 AndroidAutoSize 的使用方法后,相信大家一定很好奇,它背后的工作原理是什么呢?为什么通过简单的配置,就能实现如此神奇的屏幕适配效果呢?接下来,我们就一起深入探究 AndroidAutoSize 的原理。

(一)重要单位及概念

在 Android 开发中,有几个与屏幕适配密切相关的单位和概念,理解它们是掌握 AndroidAutoSize 原理的基础。

px(Pixels,像素):是屏幕上的实际像素点,是一个绝对单位。比如我们常见的屏幕分辨率 1920x1080,就表示屏幕在水平方向上有 1920 个像素点,垂直方向上有 1080 个像素点 。在不同分辨率和尺寸的手机上,同样数量的 px 所代表的物理尺寸是不同的。例如,在一个小尺寸高分辨率的手机上,100px 的按钮可能看起来很小;而在一个大尺寸低分辨率的手机上,100px 的按钮可能就会显得很大。所以,直接使用 px 进行布局,很难实现屏幕适配 。

dp(Density - independent Pixels,设备独立像素):也叫 dip,是一种与设备密度无关的虚拟像素单位。它的出现就是为了解决 px 在不同设备上显示不一致的问题。在 Android 系统中,规定以 160dpi(dots per inch,每英寸像素数)的屏幕为基准,在这种屏幕上,1dp = 1px。而在其他 dpi 的屏幕上,dp 与 px 的换算公式为:px = dp \* \(dpi / 160\)。例如,在 320dpi 的屏幕上,1dp 就等于 2px 。使用 dp 作为布局单位,能够保证在不同密度的屏幕上,相同 dp 值的 View 在屏幕上占据的物理尺寸大致相同,从而实现一定程度的屏幕适配 。

dpi(Dots Per Inch,每英寸像素数):即屏幕密度,它描述了在每英寸长度内像素点的数量。dpi 的值越高,屏幕就越清晰,像素点也就越密集。Android 系统根据 dpi 的值将屏幕分为不同的密度等级,常见的有 ldpi(\120dpi)、mdpi(\160dpi)、hdpi(\240dpi)、xhdpi(\320dpi)、xxhdpi(\480dpi)、xxxhdpi(\640dpi)等 。不同密度等级的屏幕,dp 与 px 的换算比例也不同 。

sp(Scaled Pixels,缩放像素):主要用于设置字体大小,它与 dp 类似,但还会考虑用户在系统设置中对字体大小的偏好设置。默认情况下,1sp ≈ 1dp,但当用户调整系统字体大小时,使用 sp 作为单位的字体大小会相应地进行缩放,而使用 dp 作为单位的字体大小则不会改变 。这样可以确保在不同的字体设置下,应用内的文字都能保持良好的可读性 。

这些单位之间的关系和转换公式在屏幕适配中起着至关重要的作用。AndroidAutoSize 正是基于对这些单位的理解和运用,实现了高效的屏幕适配。

(二)关键类与单位转换

在 Android 系统中,DisplayMetrics类和TypedValue类与单位转换密切相关,它们在 AndroidAutoSize 的实现中也扮演着重要角色 。

DisplayMetrics类用于描述屏幕的通用信息,包括屏幕尺寸、密度和缩放等。它包含了以下几个重要参数:

  • density:屏幕密度,即 dpi / 160,dp 与 px 之间的转换就是用此参数 。例如,在 xhdpi(320dpi)的屏幕上,density = 320 / 160 = 2

  • densityDpi:屏幕的 dpi 值 。

  • scaledDensity:字体大小转换时会用到此参数,px = sp \* scaledDensity。默认情况下,scaledDensity等于density,但当用户调整系统字体大小时,scaledDensity会随之变化 。

  • xdpiydpi:分别表示屏幕在 x 轴和 y 轴方向上每英寸的像素数 。

TypedValue类提供了将不同单位的尺寸值转换为 px 值的方法,其applyDimension方法的源码如下:

public static float applyDimension(int unit, float value, DisplayMetrics metrics) {
    switch (unit) {
        case COMPLEX_UNIT_PX:
            return value;
        case COMPLEX_UNIT_DIP:
            return value * metrics.density;
        case COMPLEX_UNIT_SP:
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT:
            return value * metrics.xdpi * (1.0f / 72);
        case COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM:
            return value * metrics.xdpi * (1.0f / 25.4f);
    }
    return 0;
}

这个方法根据传入的单位unitDisplayMetrics对象metrics,将value转换为对应的 px 值。例如,当unitCOMPLEX\_UNIT\_DIP时,会将value乘以metrics\.density,实现 dp 到 px 的转换 。

AndroidAutoSize 正是通过修改DisplayMetrics中的densitydensityDpiscaledDensity等参数,来改变系统在进行单位转换时的计算方式,从而实现屏幕适配 。

(三)核心原理剖析

AndroidAutoSize 的核心原理是通过修改DisplayMetrics中的核心数据,使得在不同分辨率手机上对应的 dp 相等,从而达到每个显示的 View 占用屏幕的比例相同 。

具体来说,假设我们的设计图宽度是 360dp,当应用运行在一个屏幕宽度为 1080px 的设备上时,AndroidAutoSize 会按照以下步骤进行适配:

  1. 计算目标 density:根据设计图宽度和设备屏幕实际宽度,计算出目标density。公式为:targetDensity = 屏幕宽度px / 设计图宽度dp。在这个例子中,targetDensity = 1080 / 360 = 3

  2. 修改 DisplayMetrics 参数:获取当前 Activity 的Resources中的DisplayMetrics对象,并将其density设置为计算出的targetDensitydensityDpi设置为\(int\) \(targetDensity \* 160\),即3 \* 160 = 480scaledDensity也设置为targetDensity(初始值,后续会根据系统字体变化进行调整) 。代码如下:

DisplayMetrics displayMetrics = activity.getResources().getDisplayMetrics();
displayMetrics.density = targetDensity;
displayMetrics.densityDpi = (int) (targetDensity * 160);
displayMetrics.scaledDensity = targetDensity;
  1. 单位转换与适配:经过上述修改后,当系统进行 dp 到 px 的转换时,就会使用新的density值。例如,一个在布局中设置为 100dp 的 View,在这个设备上转换为 px 时,px = 100 \* 3 = 300px 。而如果在另一个屏幕宽度为 1440px 的设备上,按照同样的方法计算,targetDensity = 1440 / 360 = 4,那么这个 100dp 的 View 转换为 px 就是100 \* 4 = 400px 。虽然两个设备上 100dp 的 View 对应的 px 值不同,但它们在各自屏幕宽度中所占的比例是相同的,都是100 / 360 ≈ 27\.8% 。这样就实现了 View 在不同分辨率设备上按比例缩放,达到了屏幕适配的效果 。

为了支持用户改变系统字体大小,AndroidAutoSize 还会监听系统配置变化。当字体大小改变时,会在targetDensity的基础上乘以一个字体比例系数,重新计算scaledDensity,确保字体大小也能正确适配 。

通过这样的方式,AndroidAutoSize 巧妙地利用了 Android 系统的单位转换机制,通过修改DisplayMetrics的关键参数,实现了高效、便捷的屏幕适配,让我们的应用在各种设备上都能呈现出一致、美观的界面效果 。

应用场景与优势尽显

(一)多场景适用

AndroidAutoSize 的应用场景十分广泛,几乎涵盖了所有类型的 Android 应用。

在新闻阅读类应用中,如腾讯新闻、今日头条等,需要在不同屏幕尺寸的设备上展示新闻内容、图片和评论区等。使用 AndroidAutoSize,能够确保新闻标题、正文的字体大小,图片的展示比例以及各个板块的布局在各种手机和平板上都能保持一致,为用户提供稳定的阅读体验。用户无论是在小屏幕的手机上,还是在大屏幕的平板上浏览新闻,都能清晰地看到内容,不会出现文字过小、图片变形或布局混乱的情况 。

对于社交网络应用,如微信、微博等,适配不同屏幕尺寸同样重要。聊天界面中的头像、文字消息、表情符号,以及朋友圈、动态展示页面等,都需要在各种设备上完美呈现。AndroidAutoSize 可以保证这些元素在不同设备上的大小和位置协调统一,让用户在与朋友聊天、浏览动态时,无论使用何种设备,都能获得舒适、一致的视觉感受 。

电商购物类应用,如淘宝、京东等,商品展示页面的布局和元素适配直接影响用户的购物体验。商品图片的大小、商品信息的排版、价格显示以及购买按钮的位置等,都需要在不同屏幕上准确呈现。借助 AndroidAutoSize,电商应用可以确保在各种设备上,商品展示都能吸引用户的注意力,操作按钮易于点击,从而提高用户的购物转化率 。

在游戏应用中,屏幕适配更是关乎游戏的流畅性和可玩性。游戏界面中的角色模型、地图场景、操作按钮等元素,必须在不同设备上保持合理的大小和位置。以热门游戏《王者荣耀》为例,无论是在普通手机还是高刷新率的电竞手机上,使用 AndroidAutoSize 都能保证游戏界面的各个元素清晰可见,操作按钮易于点击,让玩家能够专注于游戏,享受流畅的游戏体验 。

(二)显著优势阐述

AndroidAutoSize 之所以受到广大开发者的青睐,是因为它具有诸多显著的优势。

极低成本:使用 AndroidAutoSize,开发者只需在 AndroidManifest 中填写全局设计图尺寸,框架即可对项目中的所有页面进行适配。相比于传统的屏幕适配方式,无需为不同的屏幕尺寸创建大量的布局文件,也无需进行复杂的单位换算和逻辑判断,大大降低了开发成本和工作量。例如,在一个中等规模的应用中,如果采用传统适配方式,可能需要花费数周时间来处理屏幕适配问题;而使用 AndroidAutoSize,可能只需要一天的时间进行配置,就能实现全局适配,节省了大量的时间和人力成本 。

配置灵活:该库支持自定义适配参数,开发者可以根据具体需求调整适配策略。比如,可以选择按照宽度进行等比例适配,也可以选择按照高度进行适配;对于某些特殊的 Activity 或 Fragment,还可以单独设置其适配参数,使其与全局适配参数不同。这种灵活性使得 AndroidAutoSize 能够适应各种复杂的应用场景,满足不同开发者的个性化需求 。

兼容性强:AndroidAutoSize 可以与现有的布局系统(如 LinearLayout、RelativeLayout 等)无缝集成,不影响其他系统控件或三方库控件的正常使用。这意味着开发者在使用该库时,无需担心与项目中已有的代码和库产生冲突,可以放心地将其应用到各种项目中。即使项目中使用了一些第三方 UI 库,如 RecyclerView、CardView 等,AndroidAutoSize 也能很好地与之兼容,确保整个应用的界面在不同设备上都能正确显示 。

实时预览:在开发阶段,AndroidAutoSize 提供了布局时的实时预览功能。开发者可以在 Android Studio 的布局预览窗口中,实时查看应用在不同屏幕尺寸设备上的显示效果,方便及时发现和解决适配问题。这一功能大大提高了开发效率,减少了反复调试和测试的时间。例如,在设计一个新的界面时,开发者可以通过实时预览功能,快速调整布局参数,直到界面在各种设备上都显示完美,然后再进行后续的开发工作 。

开源社区支持:AndroidAutoSize 是一个开源项目,在 GitHub 上拥有活跃的社区支持和持续的更新维护。开发者在使用过程中遇到问题,可以在社区中寻求帮助,与其他开发者交流经验。同时,社区的持续更新也保证了该库能够紧跟 Android 系统的发展,不断优化和完善功能,为开发者提供更好的适配解决方案 。

避坑指南:使用注意事项

在享受 AndroidAutoSize 带来的便捷屏幕适配的同时,我们也不能忽视一些使用过程中的注意事项,避免在项目中出现意想不到的问题。

由于 AndroidAutoSize 是全局的屏幕适配方案,会修改系统的DisplayMetrics参数,这可能会对一些三方库和系统控件的布局产生影响 。比如,某些三方库在内部可能依赖了系统默认的density值进行布局计算,当 AndroidAutoSize 修改了density后,这些三方库的布局可能会出现异常。为了避免这种情况,在引入新的三方库时,要仔细阅读其文档,了解是否对DisplayMetrics有特殊依赖 。如果发现三方库布局异常,可以尝试在该三方库使用的相关页面或 Activity 中,通过实现CancelAdapt接口,取消 AndroidAutoSize 的适配,或者与三方库的开发者沟通,寻求解决方案 。

当调用AutoSizeConfig\.getInstance\(\)\.stop\(this\)暂停 AndroidAutoSize 后,它只是停止了对后续还没有启动的 Activity 适配的工作,但对已经启动且已经适配的 Activity 不会有任何影响 。同样,重新启动 AndroidAutoSize(AutoSizeConfig\.getInstance\(\)\.restart\(\))后,也只是重新开始对后续还没有启动的 Activity 进行适配,对已经启动且在stop期间未适配的 Activity 不会有任何影响 。这就需要我们在暂停和重启适配时,充分考虑到已启动 Activity 的状态,避免出现部分页面适配不一致的情况 。例如,如果在应用启动后,根据用户的某些操作暂停了适配,然后又重启适配,要确保新启动的 Activity 和之前已启动的 Activity 在适配效果上保持一致 。

在实现CustomAdapt接口进行自定义适配时,务必严格遵循接口方法的规则 。isBaseOnWidth方法决定了是按照宽度还是高度进行等比例适配,必须根据实际需求准确返回truefalsegetSizeInDp方法返回的设计图尺寸,要与isBaseOnWidth方法的返回值相匹配 。如果返回值错误,可能导致适配效果与预期不符,出现布局拉伸或压缩的问题 。例如,在一个以高度为基准进行适配的 Activity 中,如果isBaseOnWidth方法错误地返回了true,而getSizeInDp方法返回的是设计图的高度值,那么在适配过程中就会出现布局混乱的情况 。