Android 屏幕适配从未如此简单

作者:blankj https://juejin.im/post/5b6250bee51d451918537021

前言

一个月前看了今日头条新的屏幕适配方案,对此不禁拍案叫绝,为此我想把这种方案融入到我工具类中直接一行代码即可适配,如今最新 1.18.0 版 AndroidUtilCode:https://github.com/Blankj/AndroidUtilCode 已有其适配方案,其相关函数在 ScreenUtils 中,相关 API 如下所示:

adaptScreen4VerticalSlide  : 适配垂直滑动的屏幕
adaptScreen4HorizontalSlide: 适配水平滑动的屏幕
cancelAdaptScreen          : 取消适配屏幕

效果

UtilApk 中的 ScreenAdaptActivity 以 360dp 来做适配,代码如下所示:

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) {
        tvUp = findViewById(R.id.tv_up);
        tvDown = findViewById(R.id.tv_down);
        if (!ScreenUtils.isPortrait()) {
            updateLayout();
        }
    }

    @Override
    public void doBusiness() {

    }

    @Override
    public void onWidgetClick(View view) {

    }

    public void toggleFullScreen(View view) {
        ScreenUtils.toggleFullScreen(this);
        updateLayout();
    }

    private void updateLayout() {
        int statusBarHeight = BarUtils.getStatusBarHeight();
        int statusBarHeightInDp = SizeUtils.px2dp(this, statusBarHeight);
        ViewGroup.LayoutParams upLayoutParams = tvUp.getLayoutParams();
        ViewGroup.LayoutParams downLayoutParams = tvDown.getLayoutParams();
        if (ScreenUtils.isFullScreen(this)) {
            int height = 360 / 2;
            String s = height + "dp";
            upLayoutParams.height = SizeUtils.dp2px(this, height);
            tvUp.setLayoutParams(upLayoutParams);
            tvUp.setText(s);

            downLayoutParams.height = SizeUtils.dp2px(this, height);
            tvDown.setLayoutParams(downLayoutParams);
            tvDown.setText(s);
        } else {
            int height = 360 / 2 - statusBarHeightInDp / 2;
            String s = height + "dp";
            upLayoutParams.height = SizeUtils.dp2px(this, height);
            tvUp.setLayoutParams(upLayoutParams);
            tvUp.setText(s);

            downLayoutParams.height = SizeUtils.dp2px(this, height);
            tvDown.setLayoutParams(downLayoutParams);
            tvDown.setText(s);
        }
    }
}

其在 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 即可。

Activity 和 Application的 Resources#getDisplayMetrics 中都有 density、densityDpi、scaledDensity 这三个变量,我们在适配的时候修改的是 Activity 中相关的值,如果想要取消该屏幕适配,只需将 Application 的 Resources#getDisplayMetrics 中的 density、densityDpi、scaledDensity 这三个变量值赋予 Activity 对应的即可。

代码寥寥几行,如下所示:

/**
 * Adapt the screen for vertical slide.
 *
 * @param designWidthInDp The size of design diagram's width, in dp,
 *                        e.g. the design diagram width is 720px, in XHDPI device,
 *                        the designWidthInDp = 720 / 2.
 */
public static void adaptScreen4VerticalSlide(final Activity activity,
                                             final int designWidthInDp) {
    adaptScreen(activity, designWidthInDp, true);
}

/**
 * Adapt the screen for horizontal slide.
 *
 * @param designHeightInDp The size of design diagram's height, in dp,
 *                         e.g. the design diagram height is 1080px, in XXHDPI device,
 *                         the designHeightInDp = 1080 / 3.
 */
public static void adaptScreen4HorizontalSlide(final Activity activity,
                                               final int designHeightInDp) {
    adaptScreen(activity, designHeightInDp, false);
}

/**
 * Cancel adapt the screen.
 *
 * @param activity The activity.
 */
public static void cancelAdaptScreen(final Activity activity) {
    final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();
    final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();
    activityDm.density = appDm.density;
    activityDm.scaledDensity = appDm.scaledDensity;
    activityDm.densityDpi = appDm.densityDpi;
}

/**
 * Reference from: https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA
 */
private static void adaptScreen(final Activity activity,
                                final float sizeInDp,
                                final boolean isVerticalSlide) {
    final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();
    final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();
    if (isVerticalSlide) {
        activityDm.density = activityDm.widthPixels / sizeInDp;
    } else {
        activityDm.density = activityDm.heightPixels / sizeInDp;
    }
    activityDm.scaledDensity = activityDm.density * (appDm.scaledDensity / appDm.density);
    activityDm.densityDpi = (int) (160 * activityDm.density);
}

坑点

之前写的 SizeUtils.px2dp 工具类都是以 Application 的 context 来使用的,所以在写 Demo 横屏的时候适配状态栏发现有点问题,尺寸总是不对,最后恍然大悟 Activity 的 Resources#getDisplayMetrics 和 Application 的 Resources#getDisplayMetrics 是两个不同的引用,所以我工具类对 SizeUtils.px2dp 拓展了一个 context 参数来适配 Activity 的尺寸转换,也就是 Demo 中的代码 SizeUtils.dp2px(this, height),发现了 Application 和 Activity Resources#getDisplayMetrics 区别这点也就方便了我做取消适配和优化今日头条的实现,其实代码根本就不需要他想的那么复杂,很多事情走到头来一般都会有优雅的解决方式,而我工具类中的实现便是如此。

建议

老项目那就不要大动干戈改动适配代码了,新项目我建议采用我工具类中的使用,可以让你爽到极致,在 BaseActivity 中 setContentView(xx) 之前调用适配代码即可,再啰嗦一次,传入第二个参数就是设计图转换为 dp 尺寸的大小,比如要做水平固定,可垂直滑动的屏幕适配,设计图宽度为 1080px,你的资源是放在 drawable-xhdpi 中,那么它换算为 dp 就是 1080 / 2 = 540dp,这个 2 怎么来的那我就不道破了,这是 Android 基础,不懂的话去补补基础。

算了我还是说下这个 2 吧,先看一下下表:

DPI 等级     LDPI   MDPI    HDPI    XHDPI   XXHDPI  XXXHDPI
DPI 数值     120    160     240     320     480     640

在安卓中我们都是以 mdpi 为基准,那么 xhdpi 和 mdpi 的比值就是 320 / 160 = 2。

如果代码中涉及到了 px 和 dp、px 和 sp 互转,一定要用我工具类中 SizeUtils.dp2px、SizeUtils.px2dp、SizeUtils.sp2px、SizeUtils.px2sp 传入 context 的重载,切莫省去 context 从而导致使用 Application 的 context。建议让设计师给你做 xxhdpi 的设计图,这样可以让高清的设备有更好的显示。

有了固定的 dp 尺寸,那么我们百分比是不是就很好实现了,计算后直接写 xxdp 即可,这样在所有设备上也都是一定的比例,哪里还需要什么百分比布局什么的来做?是不是 so easy,更多风骚的操作可待你解锁。

结语

如果我的工具类对你的适配造成了影响,欢迎到 AndroidUtilCode:https://github.com/Blankj/AndroidUtilCode 提 issue,感谢今日头条的方案,让我可以站在巨人的肩膀上装一次 13。

原文发布于微信公众号 - 刘望舒(liuwangshuAndroid)

原文发表时间:2018-08-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

actionbar详解(二)

经过前面两篇文章的学习,我想大家对ActionBar都已经有一个相对较为深刻的理解了。唯一欠缺的是,前面我们都只是学习了理论知识而已,虽然知识点已经掌握了,但是...

20980
来自专栏androidBlog

自定义 Behavior - 仿新浪微博发现页的实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/de...

17620
来自专栏移动开发

点击空白处隐藏软键盘

在点击Editext的时候安卓会弹出软键盘,在我们输入完后不点击软键盘的”完成”键的时候,软键盘有时候会一直停留在”界面”,甚至跳转到另一个”界面”上.这样体验...

16820
来自专栏Android干货园

Android源码解析--SwipeMenuListView仿QQ聊天左滑

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/50...

16410
来自专栏郭霖

Android ActionBar应用实战,高仿微信主界面的设计

经过前面两篇文章的学习,我想大家对ActionBar都已经有一个相对较为深刻的理解了。唯一欠缺的是,前面我们都只是学习了理论知识而已,虽然知识点已经掌握了,但是...

32550
来自专栏Android干货

安卓开发_慕课网_百度地图_添加覆盖物

322100
来自专栏Android-薛之涛

ViewFlipper-仿淘宝垂直广告滚动

viewflipper的子布局item_viewflipper.xml,下面是效果图,自己写,不会没招。

1.2K20
来自专栏QQ音乐技术团队的专栏

Android动态布局入门及NinePatchChunk解密

摆脱XML布局文件 相信每一个Android开发者,在接触“Hello World”的时候,就形成了一个观念:Android UI布局是通过layout目录下...

62270
来自专栏Android机器圈

GridView结合tablayout实现展开收缩功能

PS:最近有一些粉丝给我留言说怎么实现那种 上面多个item,然后可以展开收缩,当点击了item后下方会出现一些数据,而且item对应多个型号,我当时看到这也...

58980
来自专栏Android干货园

Android 高仿微信群聊头像

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/49...

35020

扫码关注云+社区

领取腾讯云代金券