传统的适配方案,是根据Android中的 pxdensity,dp和dpi的关系做换算。而很多设备却并没有按屏幕尺寸、分辨率和像素密度的关系这个固有的规则来实现,得到的dpi值混乱。按传统的适配方式并不能完全解决适配的问题。因此本文探讨了一个优雅的方式来实现,核心思想是自定义density,以宽或者高一个维度去适配,保持该维度上和设计图一致。

前言

一个月前看了今日头条新的屏幕适配方案,这是传送门,对此不禁拍案叫绝,为此我想把这种方案融入到我工具类中直接一行代码即可适配,如今最新 1.18.0 版 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 为高度的适配效果。

原理

如果看了上面今日头条的那篇适配文章,那么你可能已经知道其原理了,不明白的话可以继续看下我的解释:原理就是修改 Activity 的 Resources#getDisplayMetrics 中的 density、densityDpi、scaledDensity 这三个变量,使其表现为设计图下的 dp 尺寸,因为 Activity 视图中所有尺寸的转化都会围绕上面三个变量,举个例子,我们以水平固定、垂直可滑动,设计图水平宽度为 360dp 为例子,那么我们强行把设备的 density 修改为 360,然后等比修改 densityDpi、scaledDensity 的值,那么这个 Activity 在任何设备下就强行转化为了水平宽度为 360dp 的尺寸,由于垂直可滑动,只要垂直方向是自适应高度来等比缩放那便在任何设备上效果都一致,这就达到了屏幕适配。

如果想要取消该屏幕适配,只需将 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 区别这点也就方便了我做取消适配和优化今日头条的实现,其实代码根本就不需要他想的那么复杂,很多事情走到头来一般都会有优雅的解决方式,而我工具类中的实现便是如此。

建议

老项目那就不要大动干戈改动适配代码了,新项目我建议采用我工具类中的使用,可以让你爽到极致,在 BaseActivitysetContentView(xx) 之前调用适配代码即可,再啰嗦一次,传入第二个参数就是设计图转换为 dp 尺寸的大小,比如要做水平固定,可垂直滑动的屏幕适配,该设计图宽度为 720px-XHDPI,那么它换算为 dp 就是 720 /2 = 360dp,这个 2 怎么来的那我就不道破了,这是 Android 基础,不懂的话去补补基础,如果代码中涉及到了 px 和 dp、px 和 sp 互转,一定要用我工具类中 SizeUtils.dp2pxSizeUtils.px2dpSizeUtils.sp2pxSizeUtils.px2sp 传入 context 的重载,切莫省去 context 从而导致使用 Application 的 context

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

结语

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

本文转自:https://juejin.im/post/5b6250bee51d451918537021?utm_source=gold_browser_extension

更多相关文章

  1. 资深程序员多年代码实践总结:《和Android源代码一起工作》 | Andr
  2. Android(安卓)Studio 中手把手教你设置switch/case代码块自动补
  3. 动态化部署:Android热修复之代码修复(一)
  4. 关于android textview,edittext,导致界面的卡顿
  5. android 中用代码模拟发送按键
  6. Android(安卓)App 的设计架构:MVC、MVP、MVVM 的分析
  7. 本人初学android,希望大神帮忙指点学习路线,现在好迷茫
  8. 第二行代码学习笔记——第二章(2)
  9. Android如何实现毛玻璃效果之Android高级模糊技术

随机推荐

  1. Android(安卓)官方示例:android-architect
  2. Android原生人脸识别Camera2+FaceDetecto
  3. Android(安卓)Material Design向下兼容至
  4. Android(安卓)ADT_20新建项目Android(安
  5. Android(安卓)- Android应用程序(Applica
  6. 在EeePC上运行Android!(转)(也是代码下载配
  7. Android(安卓)EditText自动获取焦点并弹
  8. android环境配置(Error generating final
  9. Android中自定义Tab的实现
  10. [置顶] 安卓如何限制横屏和竖屏