屏幕适配问题

  • 问题

    刚刚接手项目的时候,发现很多UI布局都是直接写的多少dp,完全没有考虑到适配问题,所以发现app的安装到手机上时,有些手机的ui显示是完全变形了,所以不得不想办法解决屏幕的适配问题,因为android平台碎片化的原因,需要对许多不同的机型进行适配,查了一些资料,整理了一些解决方案

  • 解决方案

    • 方案1

      针对不同的机型,我们创建不同的屏幕尺寸的的values文件夹,然后创建dimens.xml文件。那这样了就能解决针对不同的机型进行适配,但是市面上的机型太多,而且随之后面出现新的分辨率的机型时,我们又要写一套dimens,这样不仅工作量会增大,而且app的体积也会随之增大,并不是一个很好的方案
      

在这里插入图片描述

  • 方案2

    这种方案也就是很多人提到的swdp限定符的方式,即SmllestWidth限定符适配方案

    • 什么是SmllestWidth?

      移动设备是允许屏幕旋转的,当屏幕旋转时,屏幕的宽高会互换,而使用SmllestWidth方案是不区分屏幕方向的,
      它只会把屏幕宽高最小的一方当做最小宽度,这个最小宽度是根据屏幕而定的,是固定不变的。也就是无论你如何旋转,
      只需要宽高相比,把小的那一边当做最小宽度
      
  • 在SmllestWidth方案中 需要在dimens.xml写入dp值(即将对应的px转换为dp),那么这个dp值是如何计算出来的呢?

       首先我们需要直到屏幕的密度density ,可根据 density=dpi/160 获得 dpi是每英寸像素
    
       然后我们就可以根据密度(density),像素(px),计算出对应的dp  dp=px/density=px/(dpi/160)
    
    • 举个例子 屏幕的像素 1080*1920 dpi:480

        我们会发现最小宽度为1080,它的密度为:480/160=3,而最小的像素为1080px,
        所以我们最终算出的dp为:1080/3=360dp,这个360dp就被认为是最小宽度,那么屏幕会适配values-sw360dp里面的尺寸,
        若没有该尺寸,它将找小于等于他的离得最近的尺寸,以此来减少适配的误差问题
      
  • 不同的values-swdp文件夹下的dimens如何生成的?

  • 对于不同尺寸的dimens文件的生成,需要考虑到两个因素:最小宽度基准值是多少/需要适配哪些最小宽度尺寸

    • 最小宽度基准值

      以某个屏幕尺寸为基准,确定把设备的屏幕分为多少份,然后所有的屏幕都将分为一样的份数,最后根据比例确定每一份的尺寸是多
      少,这样就会生成不同对应尺寸的dimens文件。就比如说若以360dp为基准,把屏幕分为360份,每份为1dp,那屏幕的最小宽度为
      400dp,也是要将其分为360份,那么每一份的尺寸为:400/360=1.1dp。

    • 适配哪些最小宽度

      比如你想适配的最小宽度有320dp、360dp、400dp、480dp,那么方案将在res下生成values-sw320dp,values-sw360dp、
      values-sw400dp、values-sw480dp这几个资源文件夹。当在不同的尺寸的手机上运行时,将找对应的尺寸资源进行适配,若没有找
      到将会找到相近的尺寸资源进行适配,即使有一些误差,但也不会差距很大,但是每生成对应的一种尺寸,都会占用一定的app的体
      积,所以要合理的分配想要适配的尺寸

  • 优缺点

    • 优点

        *  适配率高,极低概率出现意外
        *  不会有任何性能损耗
        *  适配范围可自由控制,不会影响其他第三方库
        *  学习成本低
      
    • 缺点

        *   维护成本比较高
        *   侵入性高,每一个布局中都会引入dimen,所以后续切换其他方案时成本非常高
        *   无法覆盖全部机型,若需要全部适配,将以增大app的体积为代价      
      
  • 使用代码生成对应问文件和尺寸资源

    • DimenTypes 使用DimenTypes确认要生成那些尺寸

      public enum DimenTypes {

         //适配Android 3.2以上   大部分手机的sw值集中在  300-460之间
          DP_sw__300(300),  // values-sw300
          DP_sw__310(310),
          DP_sw__320(320),
          DP_sw__330(330),
          DP_sw__340(340),
          DP_sw__350(350),
          DP_sw__360(360),
          DP_sw__370(370),
          DP_sw__380(380),
          DP_sw__390(390),
          DP_sw__410(410),
          DP_sw__420(420),
          DP_sw__430(430),
          DP_sw__440(440),
          DP_sw__450(450),
          DP_sw__460(460),
          DP_sw__470(470),
          DP_sw__480(480),
          DP_sw__490(490),
      
          DP_sw__400(400);
         // 想生成多少自己以此类推
       
      
         /**
          * 屏幕最小宽度
          */
         private int swWidthDp;
      
      
      
      
         DimenTypes(int swWidthDp) {
      
             this.swWidthDp = swWidthDp;
         }
      
         public int getSwWidthDp() {
             return swWidthDp;
         }
      
         public void setSwWidthDp(int swWidthDp) {
             this.swWidthDp = swWidthDp;
         }
      

      }

    • 使用MakeUtils生成values文件夹以及dimens文件

      public class MakeUtils {
      
         private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n";
         private static final String XML_RESOURCE_START = "<resources>\r\n";
         private static final String XML_RESOURCE_END = "</resources>\r\n";
         private static final String XML_DIMEN_TEMPLETE = "<dimen name=\"qb_%1$spx_%2$d\">%3$.2fdp</dimen>\r\n";
      
        
         private static final String XML_BASE_DPI = "<dimen name=\"base_dpi\">%ddp</dimen>\r\n";
         private  static final int MAX_SIZE = 720;
      
         /**
          * 生成的文件名
          */
         private static final String XML_NAME = "dimens.xml";
      
      
         public static float px2dip(float pxValue, int sw,int designWidth) {
             float dpValue =   (pxValue/(float)designWidth) * sw;
             BigDecimal bigDecimal = new BigDecimal(dpValue);
             float finDp = bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).floatValue();
             return finDp;
         }
         
      
         /**
          * 生成所有的尺寸数据
          *
          * @param type
          * @return
          */
         private static String makeAllDimens(DimenTypes type, int designWidth) {
             float dpValue;
             String temp;
             StringBuilder sb = new StringBuilder();
             try {
                 sb.append(XML_HEADER);
                 sb.append(XML_RESOURCE_START);
                 //备份生成的相关信息
                 temp = String.format(XML_BASE_DPI, type.getSwWidthDp());
                 sb.append(temp);
                 for (int i = 0; i <= MAX_SIZE; i++) {
                 	
                     dpValue = px2dip((float) i,type.getSwWidthDp(),designWidth);
                     temp = String.format(XML_DIMEN_TEMPLETE,"", i, dpValue);
                     sb.append(temp);
                 }
      
      
                 sb.append(XML_RESOURCE_END);
             } catch (Exception e) {
                 e.printStackTrace();
             }
             return sb.toString();
         }
      
      
      
         /**
          * 生成的目标文件夹
          * 只需传宽进来就行
          *
          * @param type 枚举类型
          * @param buildDir 生成的目标文件夹
          */
         public static void makeAll(int designWidth, DimenTypes type, String buildDir) {
             try {
                 //生成规则
                 final String folderName;
                 if (type.getSwWidthDp() > 0) {
                     //适配Android 3.2+
                     folderName = "values-sw" + type.getSwWidthDp() + "dp";
                 }else {
                 	return;
                 }
                 
                 //生成目标目录
                 File file = new File(buildDir + File.separator + folderName);
                 if (!file.exists()) {
                     file.mkdirs();
                 }
      
                 //生成values文件
                 FileOutputStream fos = new FileOutputStream(file.getAbsolutePath() + File.separator + XML_NAME);
                 fos.write(makeAllDimens(type,designWidth).getBytes());
                 fos.flush();
                 fos.close();
      
      
             } catch (FileNotFoundException e) {
                 e.printStackTrace();
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
      

      }

    • 客户端调用

      public class DimenGenerator {
      
          /**
           * 设计稿尺寸(将自己设计师的设计稿的宽度填入)
           */
          private static final int DESIGN_WIDTH = 375;
      
          /**
           * 设计稿的高度  (将自己设计师的设计稿的高度填入)
           */
          private static final int DESIGN_HEIGHT = 667;
      
          public static void main(String[] args) {
              int smallest = DESIGN_WIDTH>DESIGN_HEIGHT? DESIGN_HEIGHT:DESIGN_WIDTH;  //     求得最小宽度
              DimenTypes[] values = DimenTypes.values();
              for (DimenTypes value : values) {
                  File file = new File("");
                  MakeUtils.makeAll(smallest, value, file.getAbsolutePath());
              }
      
          }
      
      }
      
    • 运行后生成的文件

在这里插入图片描述

  • 方案3

    • 该方案是根据今日头条的适配方案而来的,即今日头条适配方案,该方案已经开源,可直接依赖使用

    • 方案的使用( https://github.com/JessYanCoding/AndroidAutoSiz

      • 1.添加依赖库

           implementation 'me.jessyan:autosize:1.1.2'
        
    • 2.在清单文件中注册设计图的宽高尺寸(dp)

         <manifest>
             <application>            
                 <meta-data
                     android:name="design_width_in_dp"
                     android:value="360"/>
                 <meta-data
                     android:name="design_height_in_dp"
                     android:value="640"/>           
              </application>           
         </manifest>
      
    • 3.根据对应的屏幕尺寸在对应的布局中布局控件

    • 4.若某个Activity的设计尺寸与清单文件中的尺寸不一样时,可以实现CustomAdapt接口,指定特定的屏幕尺寸(dp)

         public class CustomAdaptActivity : AppCompatActivity , CustomAdapt {
         
             //是否以屏幕宽度为适配基准
             @Override
             public boolean isBaseOnWidth() {
                 return false;
             }
             //适配的尺寸是多少(dp) 
             @Override
             public float getSizeInDp() {
                 return 667;
             }
         }
      
    • 5.若某个Activity不想适配,可以实现CancelAdapt接口

         public class CancelAdaptActivity : AppCompatActivity , CancelAdapt {
         
         }
      
    • 6.可以在 pt、in、mm 这三个冷门单位中,选择一个作为副单位,副单位是用于规避修改 DisplayMetrics#density 所造成的对于其他使用 dp 布局的系统控件或三方库控件的不良影响,使用副单位后可直接填写设计图上的像素尺寸,不需要再将像素转化为 dp

          AutoSizeConfig.getInstance().getUnitsManager()
                 .setSupportDP(false)
                 .setSupportSP(false)
                 .setSupportSubunits(Subunits.MM);
      
    • 代码混淆

         -keep class me.jessyan.autosize.** { *; }
         -keep interface me.jessyan.autosize.** { *; }
      
  • 优缺点

    • 优点

            * 使用成本低,操作简单,无需在布局时添加额外的操作和代码
            
            *  侵入性低,该方案和项目完全解耦 
      
            *  由于修改的density在整个项目是全局的,所以只要一次修改,项目中所有地方都会受益,包含第三方控件和系统控件
      
            *  没有任何性能的损耗
      
    • 缺点

           *  因只修改一次density,项目中所有的地方都会自动适配,所以也会作用于第三方控件,但是若第三方控件的尺寸和当前项目的设计尺寸差距非常大时,那么适配会出现很严重的问题,若需要解决,则可以取消当前Activity的适配效果,采用其他适配方案
      
  • 设备屏幕信息
    https://material.io/tools/devices/

  • 原理解析

        *  px:即像素,1px代表屏幕上一个物理像素点
        *  dp(dip):设备无关像素,只要针对控件尺寸大小 dp=px/density
        *  sp:与缩放无关的抽象像素,类似于dp,但它主要针对文字尺寸大小
        *  density:屏幕密度,每一个设备的density都是固定的 density=dpi/160
    
  • 适配方案最核心的思想是:根据公式算出density

    dp=px/density
    
    density=dpi/160
    
    
    
    
    
    
    
    		public static float applyDimension(int unit, float value,
    		                                       DisplayMetrics metrics)
    		    {
    		        switch (unit) {
    		        case COMPLEX_UNIT_PX://单位为px  直接返回value
    		            return value;
    		        case COMPLEX_UNIT_DIP://单位为dip   px=value*metrics.desity   px=dp*density
    		            return value * metrics.density;
    		        case COMPLEX_UNIT_SP://单位为sp  px=sp*metrics.scaledDensity
    		            return value * metrics.scaledDensity;
    		        case COMPLEX_UNIT_PT:
    		            return value * metrics.xdpi * (1.0f/72);
    		        case COMPLEX_UNIT_IN:
    		            return value * metrics.xdpi;
    		        case COMPLEX_UNIT_MM:
    		            return value * metrics.xdpi * (1.0f/25.4f);
    		        }
    		        return 0;
    		    }
    
  • 根据源码可知,无论使用任何单位,最终都会转化为px

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值