Android 屏幕适配从未如斯简单(已废弃该使用方式)

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) 下的效果如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其在 768x1280 320dpi(xhdpi) 下的效果如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其在 480x800 240dpi(hdpi) 下的效果如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其在 320x480 160dpi(mdpi) 下的效果如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如上就是竖屏以 360dp 为宽度和横屏以 360dp 为高度的适配效果。

原理

如果看了上面今日头条的那篇适配文章,那么你可能已经知道其原理了,不明白的话可以继续看下我的解释: 我们知道 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() 中获取 densitydensityDpiscaledDensity 即可,而且在修改系统字体的时候,Resources#getSystem()#getDisplayMetrics() 也会相应地改变,这样也就不需要注册 registerComponentCallbacks 来监听系统字体的改变,所以最终的源码很是简洁,但其中间遇到的问题很是复杂,光工具类我这些天就更新了很多版本来解决其问题,从1.18.01.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.

m.density = systemDm.density;
appDm.scaledDensity = systemDm.scaledDensity;
appDm.densityDpi = systemDm.densityDpi;
}
/**

  • Return whether adapt screen.
  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值