一个月前看了今日头条新的屏幕适配方案,这是传送门,对此不由拍案叫绝,为此我想把这种方案融入到我工具类中直接一行代码便可适配,现在最新 1.19.0 版 AndroidUtilCode 已有其最新的适配方案,其相关函数在 ScreenUtils 中,相关 API 以下所示:java
adaptScreen4VerticalSlide : 适配垂直滑动的屏幕
adaptScreen4HorizontalSlide: 适配水平滑动的屏幕
cancelAdaptScreen : 取消适配屏幕
isAdaptScreen : 是否适配屏幕
复制代码
UtilApk 中的 ScreenAdaptActivity 以设计图为 360dp 宽度 来作适配,咱们设置两个 view 宽度为 180dp,代码以下所示:android
public class ScreenAdaptActivity extends BaseActivity {
private TextView tvUp;
private TextView tvDown;
public static void start(Context context) {
Intent starter = new Intent(context, ScreenAdaptActivity.class);
context.startActivity(starter);
}
@Override
public void initData(@Nullable Bundle bundle) {
if (ScreenUtils.isPortrait()) {
ScreenUtils.adaptScreen4VerticalSlide(this, 360);
} else {
ScreenUtils.adaptScreen4HorizontalSlide(this, 360);
}
}
@Override
public int bindLayout() {
return R.layout.activity_screen_adapt;
}
@Override
public void initView(Bundle savedInstanceState, View contentView) {
}
@Override
public void doBusiness() {
}
@Override
public void onWidgetClick(View view) {
}
public void toggleFullScreen(View view) {
ScreenUtils.toggleFullScreen(this);
}
@Override
protected void onDestroy() {
ScreenUtils.cancelAdaptScreen(this);
super.onDestroy();
}
}
复制代码
其在 1080x1920 420dpi(xxhdpi) 下的效果以下所示:git
其在 768x1280 320dpi(xhdpi) 下的效果以下所示:github
其在 480x800 240dpi(hdpi) 下的效果以下所示:bash
其在 320x480 160dpi(mdpi) 下的效果以下所示:app
如上就是竖屏以 360dp 为宽度和横屏以 360dp 为高度的适配效果。ide
若是看了上面今日头条的那篇适配文章,那么你可能已经知道其原理了,不明白的话能够继续看下个人解释: 咱们知道 px = dp * density
,咱们要适配的话须要确保 dp 不变去修改 density,而安卓默认 density = dpi / 160
,其意思就是 1dp 有多少 px,也就是像素密度,咱们开发是按照一份设计稿来作的,那么有没有什么办法来让 density 和设计稿尺寸作联系呢?假设咱们设计稿是宽度是 1080px,资源放在 xxhdpi,那么咱们宽度转换为 dp 就是 1080 / 3 = 360dp,要在不一样设备上宽度都表现为 360dp,那么就须要修改其 density = screenWidthPx / 360
,这样就知足了上述条件,而和 density 相关的还有 densityDpi、scaledDensity,咱们根据 density 等比修改 densityDpi、scaledDensity 便可。函数
因为 API 26 及以上的 Activity#getResources()#getDisplayMetrics()
和 Application#getResources()#getDisplayMetrics()
是不一样的引用,因此在 API 26 及以上适配是没有影响的,但在 API 26 如下 Activity#getResources()#getDisplayMetrics()
和 Application#getResources()#getDisplayMetrics()
是相同的引用,致使适配有问题,这里要感谢 @MirkoWu 提出的问题,后面会有解决方案。工具
若是咱们以 xxhdpi 的 360dp 来适配的话,首先在 AS 中预览是个问题,在接入第三方 SDK 带有界面或者 View 的话会致使它的尺寸全然不对,由于咱们那样适配后界面宽度只有 360dp,而第三方 SDK 中颇有可能写的布局会超出 360dp,这便会引起新的问题,固然这也是有响应的解决之道,好比暂时取消适配,但咱们有更好的方式,着重看下面介绍。布局
我着重推荐以 mdpi 为特例来适配,好比前面说到的 xxhdpi 的 360dp,那么在 mdpi 下就是 360 * 3 = 1080dp,这样咱们新建一个宽为 1080px 的 mdpi 设备(能够经过修改设备尺寸来达到 mdpi),而后切换为该设备来预览布局就完美解决了以上问题,咱们在写布局的时候设计图是 36px,那么咱们直接就写 36dp 便可,设计图字体是 24px, 咱们直接就写 24sp 便可,这样即可达到和设计图一致的效果。另外,图片资源放在须要适配的最高 dpi 下面便可,好比 drawable-xxhdpi
或者 drawable-xxxhdpi
,这样在高清屏上也不会致使失真。
可是这样会致使获取状态栏和导航栏高度有问题,其获取状态栏高度代码为以下所示:
public static int getStatusBarHeight() {
Resources resources = Utils.getApp().getResources();
int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
return resources.getDimensionPixelSize(resourceId);
}
复制代码
因为使用的是 Application#getResources,这会致使最后计算状态栏高度使用的是修改事后的 density,在这里也要感谢 @magic0908 无心间提到的 Resources.getSystem()
来获取系统的 Resources
,果不其然能够获取到正确高度的状态栏高度,代码以下所示:
public static int getStatusBarHeight() {
Resources resources = Resources.getSystem();
int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
return resources.getDimensionPixelSize(resourceId);
}
复制代码
同理获取导航栏高度也能够这样。
考虑到了 Resources.getSystem()
,那么咱们在适配上岂不是能够更方便,不用区分版本什么的 Activity#getResources()#getDisplayMetrics()
和 Application#getResources()#getDisplayMetrics()
,也不须要什么中间变量来记录适配前的值,那些值咱们直接在 Resources#getSystem()#getDisplayMetrics()
中获取 density
、densityDpi
、scaledDensity
便可,并且在修改系统字体的时候,Resources#getSystem()#getDisplayMetrics()
也会相应地改变,这样也就不须要注册 registerComponentCallbacks
来监听系统字体的改变,因此最终的源码非常简洁,但其中间遇到的问题非常复杂,光工具类我这些天就更新了不少版原本解决其问题,从1.18.0
到 1.18.7
,有六个版本都是和这个适配有关系,但最终仍是完美地找到了解决方案,也要感谢你们的帮助,其最终源码以下所示:
/** * Adapt the screen for vertical slide. * * @param activity The activity. * @param designWidthInPx The size of design diagram's width, in pixel. */
public static void adaptScreen4VerticalSlide(final Activity activity, final int designWidthInPx) {
adaptScreen(activity, designWidthInPx, true);
}
/** * Adapt the screen for horizontal slide. * * @param activity The activity. * @param designHeightInPx The size of design diagram's height, in pixel. */
public static void adaptScreen4HorizontalSlide(final Activity activity, final int designHeightInPx) {
adaptScreen(activity, designHeightInPx, false);
}
/** * Reference from: https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA */
private static void adaptScreen(final Activity activity, final int sizeInPx, final boolean isVerticalSlide) {
final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();
final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();
final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();
if (isVerticalSlide) {
activityDm.density = activityDm.widthPixels / (float) sizeInPx;
} else {
activityDm.density = activityDm.heightPixels / (float) sizeInPx;
}
activityDm.scaledDensity = activityDm.density * (systemDm.scaledDensity / systemDm.dens
activityDm.densityDpi = (int) (160 * activityDm.density);
appDm.density = activityDm.density;
appDm.scaledDensity = activityDm.scaledDensity;
appDm.densityDpi = activityDm.densityDpi;
}
/** * Cancel adapt the screen. * * @param activity The activity. */
public static void cancelAdaptScreen(final Activity activity) {
final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();
final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();
final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();
activityDm.density = systemDm.density;
activityDm.scaledDensity = systemDm.scaledDensity;
activityDm.densityDpi = systemDm.densityDpi;
appDm.density = systemDm.density;
appDm.scaledDensity = systemDm.scaledDensity;
appDm.densityDpi = systemDm.densityDpi;
}
/** * Return whether adapt screen. * * @return {@code true}: yes<br>{@code false}: no */
public static boolean isAdaptScreen() {
final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();
final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();
return systemDm.density != appDm.density;
}
复制代码
在原理里都已经说完了哈。
新老项目均可以用这套方案,老项目中若是有新的 Activity 加进来,那么能够对其使用该方案来适配,而后在启动其余老的 Activity 时候 cancelAdaptScreen
便可。新项目我建议采用我工具类中的使用,可让你爽到极致,在 BaseActivity
中 setContentView(xx)
以前调用适配代码便可,记得第二个参数必定要传入设计图的实际像素尺寸,再也不是曾经的 dp 尺寸了。
有了固定的尺寸,那么咱们百分比是否是就很好实现了,计算后直接写 xxdp 便可,这样在全部设备上也都是必定的比例,哪里还须要什么百分比布局什么的来作?是否是 so easy,更多风骚的操做可待你解锁。
若是个人工具类对你的适配形成了影响,欢迎到 AndroidUtilCode 提 issue,感谢今日头条的方案,让我能够站在巨人的肩膀上装一次 13。
最后
记得屏幕适配必定要用 1.19.0 版本及以上
记得屏幕适配必定要用 1.19.0 版本及以上
记得屏幕适配必定要用 1.19.0 版本及以上
给你们带来了麻烦,sorry。