美文网首页
屏幕适配

屏幕适配

作者: 放肆滴微笑 | 来源:发表于2019-11-05 16:10 被阅读0次

    1. 什么是屏幕尺寸,屏幕分辨率,屏幕像素密度

    1.1屏幕尺寸:
    屏幕对角线长度,单位是英寸,比如4.7手机,5寸手机,6寸手机


    image.png

    1.2屏幕分辨率:
    如1920*1080,指在手机屏幕上的像素点个数单位是px,1px=1像素点。


    image.png
    1.3 屏幕像素密度:
    指每英寸上的像素点个数,单位是dpi。像素密度与屏幕尺寸和屏幕分辨率有关。
    image.png

    2. dp、dip、dpi、sp、px

    • dp:Android特有的,意为密度无关像素,Google 发布的 BASELINE(基准线)为 160,以此为基准。1dip=1px或1dp=1px。480 * 320 160dpi 那么这台机器上的1DP会被翻译成1px
      800 * 480 240dpi 而这台机器上的1DP会被翻译成1.5px
    • dip:(Density Independent Pixels)同dp一个意思
    • dpi:屏幕像素密度
    • sp:(Scale-IndependentPixels)可以根据文字大小首选项自动进行缩放。Google推荐我们使用12sp以上的大小,通常使用12sp,14sp,18sp,22sp,为避免精度损失,建议最好不要使用奇数和小数
    • px:构成涂销的最小单位,常说的像素
      名称                 像素密度范围         图片大小
      mdpi                 120dp~160dp         48×48px
      hdpi                 160dp~240dp         72×72px
      xhdpi                240dp~320dp         96×96px
      xxhdpi               320dp~480dp         144×144px
      xxxhdpi              480dp~640dp         192×192px
    
    image.png

    在Google官方开发文档中,说明了 mdpi:hdpi:xhdpi:xxhdpi:xxxhdpi=2:3:4:6:8 的尺寸比例进行缩放。例如,一个图标的大小为48×48dp,表示在mdpi上,实际大小为48×48px,在hdpi像素密度上,实际尺寸为mdpi上的1.5倍,即72×72px,以此类推,可以继续往后增加,不过一般情况下已经够用了,这种用来去适配手机和平板之间的图形问题

    3. 屏幕适配

    • 限定符
    限定符分类:
        屏幕尺寸    
            small   小屏幕
            normal  基准屏幕
            large   大屏幕
            xlarge  超大屏幕
        屏幕密度
            ldpi    <=120dpi
            mdpi    <= 160dpi
            hdpi    <= 240dpi
            xhdpi   <= 320dpi
            xxhdpi  <= 480dpi
            xxhdpi  <= 640dpi(只用来存放icon)
            nodpi   与屏幕密度无关的资源.系统不会针对屏幕密度对其中资源进行压缩或者拉伸
            tvdpi   介于mdpi与hdpi之间,特定针对213dpi,专门为电视准备的,手机应用开发不需要关心这个密度值.
        屏幕方向    
            land    横向
            port    纵向
        屏幕宽高比   
            long    比标准屏幕宽高比明显的高或者宽的这样屏幕
            notlong 和标准屏幕配置一样的屏幕宽高比
    

    3.1、 屏幕分辨率限定符(不推荐)

    屏幕分辨率限定符适配需要再res文件夹下创建各种屏幕分辨率对应的valuse-xxx,如图

    image.png
    然后根据一个基准分辨率,例如基准1280*720,将宽度分成720份,取值1px720px,将高度分为1280份,取值1px1280px,生成各种分辨率对应的dimens.xml
    image.png
    加上设计图上空间宽度为600px,那布局中就写android:layout_width="@dimen/x600" ,当运行程序时,就会找对应的valuse下面对应的dimens,如720手机找720下的x600,1080则找1080下的x600,从而达到屏幕的适配。

    3.2 smallestWidth 限定符(推荐)

    smallestWidth 限定符适配原理与屏幕分辨率限定符适配原理一样,系统都是根据限定符去寻找对应的 dimens.xml 文件。例如程序运行在最小宽度为 360dp 的设备上,系统会自动找到对应的 values-sw360dp 文件夹下的 dimens.xml 文件。区别就在于屏幕分辨率限定符适配是拿 px 值等比例缩放,而 smallestWidth 限定符适配是拿 dp 值来等比缩放而已。需要注意的是“最小宽度”是不区分方向的,即无论是宽度还是高度,哪一边小就认为哪一边是“最小宽度”。如下分别为最小宽度为 360dp 与最小宽度为 640dp 所对应的 dimens.xml 文件:

    最小宽度的计算
    假如当前设备为1920*1080分辨率 5.5寸手机
    1920*1920+1080*1080 = 4852800
    开根约2202...
    像素密度DPI=2202/5.5=400
    400/160 = 2.5  因为160是基准
    1080/2.5 = 432   这就是最小宽度dp
    
    DisplayMetrics dm = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(dm);
    int heightPixels = ScreenUtils.getScreenHeight(this);
    int widthPixels = ScreenUtils.getScreenWidth(this);
    float density = dm.density;
    float heightDP = heightPixels / density;
    float widthDP = widthPixels / density;
    float smallestWidthDP;
    if(widthDP < heightDP) {
        smallestWidthDP = widthDP;
    }else {
        smallestWidthDP = heightDP;
    }
    
    image.png

    3.3 为什么选择samallWidth限定符

    既然原理都一样,都需要多套dimens.xml文件,为啥选择samallWidth不选择屏幕分辨率限定符呢?
    1、屏幕分辨率限定符适配根据分辨率的,android设备分辨率非常多,还要考虑虚拟键盘,就需要大量dimens.xml,就19201080是无虚拟键盘,有无虚拟键盘就变成18121080。而无论手机像素是多少,密度多少,90%手机的最小宽度都是360dp所以采用samallWidth,就变成少量dimens.xml文件。
    2、屏幕分辨率是px单位,而samallWidth是dp和sp,Google推荐使用dp和sp,sp又能随着设置系统大小而更改字体大小,使用起来更灵活。
    3、屏幕分辨率需要valuse-xxx完全匹配才能达到适配,而samallWidth限定符寻找dimens.xml的原理是从大忘小的找,假如设备最小宽度是411dp,就会先找valuse-411dp,发现没有,才会像下找valuse-410~~400,如果没有,才会去valuse默认里面找。

    4. 屏幕适配解决方案

    4.1 系统布局方案

    • 避免写死控件尺寸,使用wrap_content, match_parent
    • LinearLayout xxx:weight="0.5“
    • RelativeLayout xxx:layout_centerInParent="true" ...
    • ContraintLayout xxx:layout_constraintLeft_toLeftOf="parent"...

    4.2 像素密度适配

    1、在BaseActivity方法中调用DensityUtils.setDensity(getApplication(), this);
    2、或者在Activity的生命周期中调用

    registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
                @Override
                public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                    DensityUtils.setDensity(MyApplication.this, activity);
                }
    

    下面为像素密度的适配代码

        private static final float WIDTH = 360;//参考像素密度(dp)
        private static float appDensity;//表示屏幕密度
        private static float appScaleDensity;//字体缩放比例,默认为appDensity
    
        public static void setDensity(final Application application, Activity activity) {
            //获取当前屏幕信息
            DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
            if (appDensity == 0) {
                //初始化赋值
                appDensity = displayMetrics.density;
                appScaleDensity = displayMetrics.scaledDensity;
                //监听字体变化
                application.registerComponentCallbacks(new ComponentCallbacks() {
                    @Override
                    public void onConfigurationChanged(Configuration newConfig) {
                        //字体发生更改,重新计算scaleDensity
                        if (newConfig != null && newConfig.fontScale > 0) {
                            appScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
                        }
                    }
    
                    @Override
                    public void onLowMemory() {
    
                    }
                });
            }
            //计算目标density scaledDensity
            float targetDensity = displayMetrics.widthPixels / WIDTH;//1080/360=3;
            float targetScaleDensity = targetDensity * (appScaleDensity / appDensity);
            int targetDensityDpi = (int) (targetDensity * 160);
            //替换Activity的值
            //px = dp * (dpi / 160)
            DisplayMetrics dm = activity.getResources().getDisplayMetrics();
            dm.density = targetDensity;  //(dpi/160) 后得到的值
            dm.scaledDensity = targetScaleDensity;
            dm.densityDpi = targetDensityDpi;  //dpi
        }
    

    4.3 通过smallestWidth 限定符来适配

    5 刘海屏适配

    1、判断有没有刘海屏,需要再View绑定到Window后才能判断,没有绑定则拿不到数据

    NotchUtil.isHasCutout(this, new OnCutoutListener() {
                @Override
                public void isHasCutout(boolean isHas) {
                    isHasCutout = isHas;  //注释1
                    if (isHas) {
                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
                        /**
                         * * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 全屏模式,内容下移 非全屏模式下不受影响
                         * * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 允许内容进入刘海区域
                         * * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 不允许内容进入刘海区域
                         */
                        NotchUtil.setImmersiveWithNotch(BaseActivity.this, false,
                                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
                    }
                }
            });
    

    可以在注释1处获取到当前是否是刘海屏
    判断刘海屏实际代码
    因为刘海屏是在28之后才出现的,Google在28之后也出了专门获取刘海屏的类

    /**
         * 判断是否有刘海屏
         * PS:因为Android P是在回调中判断的,所以,使用listener形式
         * 并且,只有在切换时才会获取到,否则返回都是false(API 28)
         *
         * @param app      {@link AppCompatActivity}
         * @param listener {@link OnCutoutListener}
         */
        public static void isHasCutout(AppCompatActivity app, OnCutoutListener listener) {
            if (Build.VERSION.SDK_INT >= 28) {
                NotchThirdUtil.getNotchSize4Google(app, listener);
            } else {
                if (OSUtil.isEmui()) {
                    listener.isHasCutout(NotchThirdUtil.hasNotchInScreenAtHuawei(app));
                } else if (OSUtil.isOppo()) {
                    listener.isHasCutout(NotchThirdUtil.hasNotchInScreenAtOppo(app));
                } else if (OSUtil.isVivo()) {
                    listener.isHasCutout(NotchThirdUtil.hasNotchInScreenAtVoio(app));
                } else if (OSUtil.isMiui()) {
                    listener.isHasCutout(NotchThirdUtil.hasNotchInScreenAtXiaomi());
                }
            }
        }
    
    

    2、在得到是否有刘海屏后,如果有,则通过设置布局的padding属性,将布局下移状态栏的高度,没有则不处理

    @Override
        public void onAttachedToWindow() {
            super.onAttachedToWindow();
            if(getIsHasCutout()){  //有刘海屏,则下移
                RelativeLayout layout = findViewById(R.id.layout);
                layout.setPadding(0, StatusBarUtil.getStatusBarHeight(this)
                        , 0, 0);
            }
        }
    

    相关文章

      网友评论

          本文标题:屏幕适配

          本文链接:https://www.haomeiwen.com/subject/qnysbctx.html