Android屏幕自适应

Android屏幕适配出现的原因

为什么Android需要进行屏幕适配?

由于Android系统的开放性,任何用户、开发者、OEM厂商、运营商都可以对Android进行定制,修改成他们想要的样子,但是这种“碎片化”到底到达什么程度呢?


统计数据表明:
2012年,支持Android的设备共有3997种;
2013年,支持Android的设备共有11868种;
2014年,支持Android的设备共有18796种;
2015年,支持Android的设备共有24093种;

试想一下这么一个场景:
为5.5寸屏幕准备的UI设计图,运行在6.6寸的屏幕上,很可能在右侧和下侧存在大量的空白;
而6.6寸的UI设计图运行到5.5寸的设备上,很可能显示不下。


屏幕种类这么多,那么就需要一套完美的方案去解决适配问题,介绍屏幕适配方案之前,下面先简单介绍下Android屏幕中用到的一些相关概念

屏幕基本概念

屏幕尺寸

屏幕尺寸即手机屏幕对角线的物理尺寸,以英寸为单位,1英寸=2.54厘米;
现常见的屏幕尺寸:5.5、6.0、6.1、6.5、6.6、6.67、6.7英寸等等
在这里插入图片描述

屏幕分辨率和像素

屏幕分辨率就是手机屏幕的像素点数,指屏幕横竖各有多少个像素,一般描述成屏幕的“高×宽”,如下方所示屏幕分辨率就是所谓的 1080x2400 ;


屏幕像素即 1080x2400=2592000,分辨率为 1080x2400 的手机约有260w个像素;
现Android手机比较常见的分辨率为:1080x1920、1080x2400、1440x3200;


adb命令查看手机屏幕分辨率:adb shell wm size
在这里插入图片描述

sp

sp:根据文字大小首选项进行放缩,主要用于字体显示;谷歌开发指导中推荐使用12sp以上的字体,否则用户将无法看清。设置字体大小时不要使用奇数或小数,可能会造成精度丢失;

px

px:像素点,构成图像的最小单位

dp 密度无关像素

dp:安卓中的相对大小,dp就是为了使得开发者设置的长度能够根据不同屏幕的分辨率获得不同的像素(px)数量;
换算公式是 1dp=(dpi/160)x 1px;(例:在密度为160dpi的屏幕上,1px就是1dp。依次类推,在320dpi的屏幕上,1dp就是2px。屏幕密度越大,1个dp对应的px也就越大);

dpi

dpi:指每英寸的像素点数,用于描述屏幕的显示特性,dpi越大,屏幕的精细度越高,屏幕看起来就越清晰;
在这里插入图片描述


adb命令查看手机屏幕密度:adb shell wm density
在这里插入图片描述

密度类型分辨率dpidp换算
低密度(ldpi)240x3201201dp=0.75px
中密度(mdpi)320x4801601dp=1px
高密度(hdpi)480x8002401dp=1.5px
超高密度(xhdpi)720x12803201dp=2px
超超高密度(xxhdpi)1080x19204801dp= 3px

Density

Density:表示设备屏幕密度的值,它表示每英寸在屏幕上显示的像素数量,density和dpi的关系为 density = dpi/160;

屏幕方向

横屏竖屏自动切换

在res目录下建立 layout 和 layout-land 两个目录,里面分别放置竖屏和横屏两种布局文件,以适应对横屏竖屏自动切换;

禁用自动切换屏幕方向

// 限制此页面横屏显示
Android:screenOrientation="landscape"

// 限制此页面竖屏显示
Android:screenOrientation="portrait"

Android屏幕自适应

1. dp原生方案

Android尺寸的基本单位,Android自带的原始的适配方案, 在不同的分辨率手机里面表现出相应大小的像素点。 在不同的分辨率的手机里面1dp对应着不同的px, 这样就实现了dp定义一个控件大小的时候,在不同分辨率手机里表现出相应大小的像素值;

2. 线形布局权重

(layout_weight:属性值越小 对应的优先级越高)
在 values 文件夹下的 styles 文件中新建一个 style 属性标签;
布局文件中有需要引用即可(省去标签中的 android:layout_height=“0px” 属性);

示例代码

styles.xml 文件

<!--styles.xml-->
<?xml version="1.0" encoding="utf-8"?>
<resources>  
 
<!-- 全屏幕拉伸-->
  <style name="layout_full">  
    <item name="android:layout_width">match_parent</item>  
    <item name="android:layout_height">match_parent</item>  
  </style>
   
<!-- 固定自身大小-->
  <style name="layout_wrap">  
    <item name="android:layout_width">wrap_content</item>  
    <item name="android:layout_height">wrap_content</item>  
  </style>
 
<!-- 横向分布-->
  <style name="layout_horizontal" parent="layout_full">  
    <item name="android:layout_width">0px</item>  
  </style> 
    
<!-- 纵向分布-->
  <style name="layout_vertical" parent="layout_full">  
    <item name="android:layout_height">0px</item>  
  </style> 
  
</resources>  

activity_main.xml 文件

<!--activity_main.xml-->
<LinearLayout 
  style="@style/layout_vertical"
  android:layout_weight="2"
  android:orientation="vertical">
  
  <View
    style="@style/layout_vertical"
    android:background="#ffffff"
    android:layout_weight="2"/>    
  
  <View
    style="@style/layout_vertical"
    android:background="#000000"
    android:layout_weight="6"/>
  
  <View
    style="@style/layout_vertical"
    android:background="#ff0000"
    android:layout_weight="9"/>
  
  <View
    style="@style/layout_vertical"
    android:background="#00ffff"
    android:layout_weight="1"/>
 
</LinearLayout>

3. Java代码中设置宽高

示例代码

activity_main.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  <Button  
    android:id="@+id/btn1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#000000"
    android:text="按钮1"/>
  <Button  
    android:id="@+id/btn2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#ffffff"
    android:text="btn2"/>
  <Button  
    android:id="@+id/btn3"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#ff0000"
    android:text="btn3"/>
  <Button  
    android:id="@+id/btn4"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#ff00ff"
    android:text="btn4"/>   
</LinearLayout>

MainActivity.java 文件

// app启动时先获取当前屏幕的宽高
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
phoneWidth = displayMetrics.widthPixels;
phoneHeight = displayMetrics.heightPixels;

// 设置控件大小
// 第一个按钮,宽度100%,高度10%
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
    LayoutParams.MATCH_PARENT,(int) (phoneHeight * 0.1f + 0.5f));
btn1.setLayoutParams(params);

// 第二个按钮,宽度100%,高度30%
LinearLayout.LayoutParams params2 = new LinearLayout.LayoutParams(
    LayoutParams.MATCH_PARENT,(int) (phoneHeight * 0.3f + 0.5f));
btn2.setLayoutParams(params2);

// 第三个按钮,宽度50%,高度20%
LinearLayout.LayoutParams params3 = new LinearLayout.LayoutParams(
    (int) (phoneWidth * 0.5f + 0.5f),(int) (phoneHeight * 0.2f + 0.5f));
btn3.setLayoutParams(params3);

// 第三个按钮,宽度70%,高度填满剩下的空间
LinearLayout.LayoutParams params4 = new LinearLayout.LayoutParams(
    (int) (phoneWidth * 0.7f + 0.5f),LayoutParams.MATCH_PARENT);
btn4.setLayoutParams(params4);

4. smallestWidth

sw限定符适配(需要安装 SmallestWidth Dimens 插件),依据最小宽度限定符,指的是 Android 会识别屏幕宽度最小尺寸的 dp 值(其实就是手机的宽度值),然后根据识别到的结果去资源文件中寻找对应限定符的文件夹下的资源文件。


这个可以使用 Android Studio 里的 SmallestWidth Dimens 插件来完成。
需要注意的是,如果没有 values-sw360dp 文件夹,系统会向下寻找,比如离 360dp 最近的只有 values-sw340dp,那么系统就会选择 values-sw340dp 文件夹下的资源文件;
在这里插入图片描述

5. 多layout适配

多layout适配主要是针对某个分辨率,新建一个layout文件夹

6. 今日头条适配方案

适配方案的核心原理在于根据dp和px的转换公式 :px = dp * density,不管我们设定的单位是什么, 最终我们都会将这些单位长度转化为px的。
density就是他们的转化比, 所以,动态改变这个转化比也是可以达到我们适配屏幕的目的。
通过修改density值,强行把所有不同尺寸分辨率的手机的宽度dp值改成一个统一的值(在清单文件中定义),这样就解决了所有的适配问题;


Density = 当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) ;
例:分辨率1080x1920,dpi为480,正常情况下计算density=dpi/160=480/160=3,此时屏幕总宽度dp=px/density=1080/3=360;


一般的应用场景就是在BaseActivity的onCreate方法中调用,即可达成全局适配;

示例代码

工具类调用方法

// 在Activity中的onCreate方法中调用,必须在setContentView()之前
CustomDensityUtil.setCustomDensity(MainActivity.this, getApplication());

今日头条适配方案工具类

public class CustomDensityUtil {
   // 系统的Density
   private static float sNoncompatDensity;
   // 系统的ScaledDensity
   private static float sNoncompatScaledDensity;

   /**
    * 今日头条适配方案
    *
    * @param activity
    * @param application
    */
   public static void setCustomDensity(Activity activity, final Application application) {
       //通过资源文件getResources类获取DisplayMetrics
       DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
       if (sNoncompatDensity == 0) {
           //保存之前density值
           sNoncompatDensity = appDisplayMetrics.density;
           //保存之前scaledDensity值,scaledDensity为字体的缩放因子,正常情况下和density相等,但是调节系统字体大小后会改变这个值
           sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;
           //监听设备系统字体切换
           application.registerComponentCallbacks(new ComponentCallbacks() {

               public void onConfigurationChanged(Configuration newConfig) {
                   if (newConfig != null && newConfig.fontScale > 0) {
                       //调节系统字体大小后改变的值
                       sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                   }
               }

               public void onLowMemory() {

               }
           });
       }

       //获取以设计图总宽度360dp下的density值
       float targetDensity = appDisplayMetrics.widthPixels / 360;
       //通过计算之前scaledDensity和density的比获得scaledDensity值
       float targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
       //获取以设计图总宽度360dp下的dpi值
       int targetDensityDpi = (int) (160 * targetDensity);
       //设置系统density值
       appDisplayMetrics.density = targetDensity;
       //设置系统scaledDensity值
       appDisplayMetrics.scaledDensity = targetScaleDensity;
       //设置系统densityDpi值
       appDisplayMetrics.densityDpi = targetDensityDpi;

       //获取当前activity的DisplayMetrics
       final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
       //设置当前activity的density值
       activityDisplayMetrics.density = targetDensity;
       //设置当前activity的scaledDensity值
       activityDisplayMetrics.scaledDensity = targetScaleDensity;
       //设置当前activity的densityDpi值
       activityDisplayMetrics.densityDpi = targetDensityDpi;
   }
}

源码

     /**
    * @param unit 要转换的单位
    * @param value 单位对应的值
    * @param metrics 显示指标
    */
   public static float applyDimension(int unit, float value,DisplayMetrics metrics){
       switch (unit) {
       case COMPLEX_UNIT_PX://单位为px
           return value;
       case COMPLEX_UNIT_DIP://单位为dp
           return value * metrics.density;
       case COMPLEX_UNIT_SP://单位为sp
           return value * metrics.scaledDensity;
       case COMPLEX_UNIT_PT://单位为pt
           return value * metrics.xdpi * (1.0f/72);
       case COMPLEX_UNIT_IN://单位为in
           return value * metrics.xdpi;
       case COMPLEX_UNIT_MM://单位为mm
           return value * metrics.xdpi * (1.0f/25.4f);
       }
       return 0;
   }

7. AndroidAutoSize 框架(原鸿神 AndroidAutoLayout 框架改进版)

简书地址

今日头条屏幕适配方案终极版

GitHub地址

AndroidAutoSize

  • 24
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是阿超

现在二师兄的肉比师父的都贵了.

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值