|
|
51CTO旗下网站
|
|
移动端

Android 屏幕适配从未如斯简单

一个月前看了今日头条新的屏幕适配方案,这是传送门,对此不禁拍案叫绝,为此我想把这种方案融入到我工具类中直接一行代码即可适配,如今最新 1.18.0 版 AndroidUtilCode 已有其适配方案,其相关函数在 ScreenUtils 中。

作者:blankj来源:掘金|2018-08-02 15:03

前言

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

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

效果

  1. public class ScreenAdaptActivity extends BaseActivity { 
  2.  
  3.     private TextView tvUp; 
  4.     private TextView tvDown; 
  5.  
  6.     public static void start(Context context) { 
  7.         Intent starter = new Intent(context, ScreenAdaptActivity.class); 
  8.         context.startActivity(starter); 
  9.     } 
  10.  
  11.     @Override 
  12.     public void initData(@Nullable Bundle bundle) { 
  13.         if (ScreenUtils.isPortrait()) { 
  14.             ScreenUtils.adaptScreen4VerticalSlide(this, 360); 
  15.         } else { 
  16.             ScreenUtils.adaptScreen4HorizontalSlide(this, 360); 
  17.         } 
  18.     } 
  19.  
  20.     @Override 
  21.     public int bindLayout() { 
  22.         return R.layout.activity_screen_adapt; 
  23.     } 
  24.  
  25.     @Override 
  26.     public void initView(Bundle savedInstanceState, View contentView) { 
  27.         tvUp = findViewById(R.id.tv_up); 
  28.         tvDown = findViewById(R.id.tv_down); 
  29.         if (!ScreenUtils.isPortrait()) { 
  30.             updateLayout(); 
  31.         } 
  32.     } 
  33.  
  34.     @Override 
  35.     public void doBusiness() { 
  36.  
  37.     } 
  38.  
  39.     @Override 
  40.     public void onWidgetClick(View view) { 
  41.  
  42.     } 
  43.  
  44.     public void toggleFullScreen(View view) { 
  45.         ScreenUtils.toggleFullScreen(this); 
  46.         updateLayout(); 
  47.     } 
  48.  
  49.     private void updateLayout() { 
  50.         int statusBarHeight = BarUtils.getStatusBarHeight(); 
  51.         int statusBarHeightInDp = SizeUtils.px2dp(this, statusBarHeight); 
  52.         ViewGroup.LayoutParams upLayoutParams = tvUp.getLayoutParams(); 
  53.         ViewGroup.LayoutParams downLayoutParams = tvDown.getLayoutParams(); 
  54.         if (ScreenUtils.isFullScreen(this)) { 
  55.             int height = 360 / 2; 
  56.             String s = height + "dp"
  57.             upLayoutParams.height = SizeUtils.dp2px(this, height); 
  58.             tvUp.setLayoutParams(upLayoutParams); 
  59.             tvUp.setText(s); 
  60.  
  61.             downLayoutParams.height = SizeUtils.dp2px(this, height); 
  62.             tvDown.setLayoutParams(downLayoutParams); 
  63.             tvDown.setText(s); 
  64.         } else { 
  65.             int height = 360 / 2 - statusBarHeightInDp / 2; 
  66.             String s = height + "dp"
  67.             upLayoutParams.height = SizeUtils.dp2px(this, height); 
  68.             tvUp.setLayoutParams(upLayoutParams); 
  69.             tvUp.setText(s); 
  70.  
  71.             downLayoutParams.height = SizeUtils.dp2px(this, height); 
  72.             tvDown.setLayoutParams(downLayoutParams); 
  73.             tvDown.setText(s); 
  74.         } 
  75.     } 

其在 1080x1920 420dpi(xxhdpi) 下的效果如下所示:

xxhdpi

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

xhdpi

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

hdpi

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

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 对应的即可。

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

  1. /** 
  2.  * Adapt the screen for vertical slide. 
  3.  * 
  4.  * @param designWidthInDp The size of design diagram's width, in dp, 
  5.  *                        e.g. the design diagram width is 720px, in XHDPI device, 
  6.  *                        the designWidthInDp = 720 / 2. 
  7.  */ 
  8. public static void adaptScreen4VerticalSlide(final Activity activity, 
  9.                                              final int designWidthInDp) { 
  10.     adaptScreen(activity, designWidthInDp, true); 
  11.  
  12. /** 
  13.  * Adapt the screen for horizontal slide. 
  14.  * 
  15.  * @param designHeightInDp The size of design diagram's height, in dp, 
  16.  *                         e.g. the design diagram height is 1080px, in XXHDPI device, 
  17.  *                         the designHeightInDp = 1080 / 3. 
  18.  */ 
  19. public static void adaptScreen4HorizontalSlide(final Activity activity, 
  20.                                                final int designHeightInDp) { 
  21.     adaptScreen(activity, designHeightInDp, false); 
  22.  
  23. /** 
  24.  * Cancel adapt the screen. 
  25.  * 
  26.  * @param activity The activity. 
  27.  */ 
  28. public static void cancelAdaptScreen(final Activity activity) { 
  29.     final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics(); 
  30.     final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics(); 
  31.     activityDm.density = appDm.density; 
  32.     activityDm.scaledDensity = appDm.scaledDensity; 
  33.     activityDm.densityDpi = appDm.densityDpi; 
  34.  
  35. /** 
  36.  * Reference from: https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA 
  37.  */ 
  38. private static void adaptScreen(final Activity activity, 
  39.                                 final float sizeInDp, 
  40.                                 final boolean isVerticalSlide) { 
  41.     final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics(); 
  42.     final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics(); 
  43.     if (isVerticalSlide) { 
  44.         activityDm.density = activityDm.widthPixels / sizeInDp; 
  45.     } else { 
  46.         activityDm.density = activityDm.heightPixels / sizeInDp; 
  47.     } 
  48.     activityDm.scaledDensity = activityDm.density * (appDm.scaledDensity / appDm.density); 
  49.     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 尺寸的大小,比如要做水平固定,可垂直滑动的屏幕适配,该设计图宽度为 720px-XHDPI,那么它换算为 dp 就是 720 /2 = 360dp,这个 2 怎么来的那我就不道破了,这是 Android 基础,不懂的话去补补基础,如果代码中涉及到了 px 和 dp、px 和 sp 互转,一定要用我工具类中 SizeUtils.dp2px、SizeUtils.px2dp、SizeUtils.sp2px、SizeUtils.px2sp 传入 context 的重载,切莫省去 context 从而导致使用 Application 的 context,如果确定不了是什么 dpi,那么你设计稿资源是放在什么 dpi 下的那就用什么就好了,比如你资源放在 drawable-xxhdpi,那么你就用设计稿尺寸 / 3 就好了。

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

【编辑推荐】

  1. 谷歌正式推出Android P第五个开发者预览版更新
  2. Android中数据的加密解密
  3. 谷歌公布7月Android各版本份额:老版本依旧坚挺
  4. 开发过程更透明,Google 将 AndroidX 移至 AOSP
  5. Android原生与H5通信
【责任编辑:未丽燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

Linux安全体系分析与编程

本书选择经典的开放源代码,全面系统地分析了Linux安全机制。本书共有17章,前10章着重介绍了Linux操作系统的安全机制及实现方法,阐述了公...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊