美文网首页安卓
屏幕适配 - pt 思路

屏幕适配 - pt 思路

作者: 前行的乌龟 | 来源:发表于2018-12-31 23:10 被阅读2次

    ps: 18 年收官了, 今年的最后一篇了,祝愿自己来年顺顺利利的,事业大步向前进!!!

    我之前公司的适配方案是 sw 方案,之前的一个前辈自己手写的 java 脚本可以自动生成相应的 values 文件夹,可惜哪个脚本找不到了,到现在我还是相当佩服前辈高超的编码水平

    但是有个问题,有时 UI 反应,适配不如 IOS 精准,sw 最小宽度适配发是去跟前周边去找对应的 values 文件夹,或多或少有 sw 最小宽度dp 值有些差距,另外不能以高度做适配,所以我现在换成 pt 适配发了

    pt 适配发的思路来源于:一种粗暴快速的Android全屏幕适配方案,作者思路之巧妙前所未有啊,我真是佩服的五体投地啊

    本文项目地址:BW_Libs


    适配效果

    宽度适配 高度适配

    高度适配这里有点坑,坑的地方是,系统给你 window 的高度时包含状态栏,但不含底部导航栏高度的,看上图2,我的高度以 1920 为基准,我不显示状态栏,然后让底部导航栏透明,大家看看正好是这个位置

    但是我们实际不知道用户的手机有没有底部导航栏这个玩意,所以高度适配天然的不合适。


    pt 核心思路

    一切的起点都是起源于 -> 系统单位转换公式

    TypedValue.applyDimension(int unit, float value, DisplayMetrics metrics)

    public static float applyDimension(int unit, float value,
                                           DisplayMetrics metrics)
        {
            switch (unit) {
            case COMPLEX_UNIT_PX:
                return value;
            case COMPLEX_UNIT_DIP:
                return value * metrics.density;
            case COMPLEX_UNIT_SP:
                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;
        }
    

    大家还记得我们学数据库时的数据注入吗,这里用的就是这个思路

    我们设想这样一个场景,Ui 出图了,是用 px 标记的,然后也没说屏幕尺寸,我们无从计算 dp 值,也不知道屏幕密度是几,这时候怎么办,明显 dp 思路不行了

    那么我们就走到死胡同没有路了嘛,不是啊,大家还记得前几年洪洋大神的百分比布局吗,我们可以用百分比的思路来做啊,既然 UI 给的 px 标记,那我们就把 view 的 px 看成屏幕宽高的百分比

    比如我们以宽度为基准,width = 750 px ,view width = 200,那么 view 的宽度我们就可以看成未来安装设备屏幕宽度的 750 分之 200,这样只要我们能在 app 中计算出来这个比例设置给 view 就能达成我们想要的效果了

    但是我们不能写一个 view 就去计算一次吧,这样平凡的刷新 UI 一个是性能底下,一个是效率极低,难么有没有一个地方可以让我们统一设置呢

    有啊,这个就是屏幕矩阵了 displayMetrics,GUP 最终也是拿到系统计算出的每一帧的 bitmap 去渲染显示出来的,所以看到矩阵不要惊讶,本来就是如此

    displayMetrics 里面有哪些参数:


    看到没有,和显示相关的都在这里面,我们只要改了 displayMetrics 里面的值,整个 app 都能生效了

    然后从哪里获取呢,有3个地方:

    Resources.getSystem().displayMetrics
    Activity.getSystem().displayMetrics
    Application.getSystem().displayMetrics
    

    注意啊,api 26 以上 Activity.getSystem().displayMetrics 和 Application.getSystem().displayMetrics 获取的同一个类,但是 26 以下就不是一个类了,Resources.getSystem().displayMetrics 这个不能改,这个改了整个手机都跟着变

    然后我们现在再去看系统的单位转换公式,这时我们就能去做了,可以看到里面有很多单位的,我们操作哪一个呢,dp 不能动,dp 动了系统自带的部分就要出问题了,所以我们选择没什么影响的 pt

    看 pt 的公式:

            case COMPLEX_UNIT_PT:
                return value * metrics.xdpi * (1.0f/72);
    

    这里我们能操作的就是 metrics.xdpi 了,大家想,我们想用 pt 表示我们的百分比,那么理想的公式就是:

            // UIWidth = UI 出图的屏幕宽度
           wantValue = displayMetrics.x  * (ptValue / UIWidth )
    

    这个我们要用 metrics.xdpi 把我们这个公式补全,那么 metrics.xdpi 就变成了一个等式了:

            metrics.xdpi = 当前屏幕宽度 / UI 屏幕宽度 * 72f
    

    这里 * 72 是补平公式,因为 原生 pt 转换公式里 / 72 了

    最终:

        fun resizeDisplayMetrics(context: Context?, autoWidth: Float, baseLine: Int) {
    
            if (context == null) {
                return
            }
    
            if (!isCanResize(context, baseLine)) {
                return
            }
    
            val screenPoint = Point()
            var resources = context.resources
            var displayMetricsonMiui = getMetricsOnMiui(resources)
    
            (context.getSystemService(WINDOW_SERVICE) as WindowManager).defaultDisplay.getSize(screenPoint)
    
            when (baseLine) {
    
                BASE_LINE_WIDTH -> {
                    resources.displayMetrics.xdpi = screenPoint.x / autoWidth * 72f
                    if (displayMetricsonMiui != null) {
                        resources.displayMetrics.xdpi = screenPoint.x / autoWidth * 72f
                    }
                }
    
                BASE_LINE_HRIGHT -> {
                    resources.displayMetrics.xdpi = screenPoint.y / autoWidth * 72f
                    if (displayMetricsonMiui != null) {
                        resources.displayMetrics.xdpi = screenPoint.y / autoWidth * 72f
                    }
                }
            }
        }
    

    不管以宽为基准,还是以高为基准,更换的都是 displayMetrics.xdpi 这个参数

    xml 使用:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.bloodcrown.bw.screenauto.WidthActivity">
    
        <TextView
            android:id="@+id/tx01"
            android:layout_width="1000pt"
            android:layout_height="300pt"
            android:background="@color/colorAccent"
            android:gravity="center"
            android:text=" 1000pt/200pt "
            tools:layout_height="300px"
            tools:layout_width="1080px"/>
    
        <TextView
            android:id="@+id/tx02"
            android:layout_width="500pt"
            android:layout_height="300pt"
            android:background="@color/colorPrimary"
            android:gravity="center"
            android:text=" 500pt/200pt "
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tx01"
            tools:layout_height="300px"
            tools:layout_width="500px"/>
    
        <TextView
            android:id="@+id/tx03"
            android:layout_width="500pt"
            android:layout_height="300pt"
            android:background="@color/colorPrimaryDark"
            android:gravity="center"
            android:text=" 500pt/200pt "
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tx01"
            tools:layout_height="300px"
            tools:layout_width="500px"/>
    
    </android.support.constraint.ConstraintLayout>
    

    使用 pt 之后,xml 预览就肯定不对了,这里我们使用 tools 设置预览时 view 宽高的 px 值,只要选对分辨率的设备,效果还是一样的,就是麻烦一些


    api 使用

    我的代码不多,自己手写下来,其实和上面我参考的文章代码差不多,区别是我添加了高度基准适配,设置可以灵活一些

    1. 若 app 只需要一种适配基准,比如宽度,那么我们统一在 application 中设置一下即可,为了兼容在 ActivityLifecycleCallbacks 回掉里再设置一下
    class MyApplication : Application() {
    
        override fun onCreate() {
            super.onCreate()
    
            ScreenAutoManager.instance.init(this, 1080.0f, ScreenAutoManager.BASE_LINE_WIDTH)
    
            this.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
    
                override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {
                    ScreenAutoManager.instance.onActivityCreated(activity)
                    Log.d("AA", "onActivityCreated")
                }
    
                override fun onActivityResumed(activity: Activity?) {
                    ScreenAutoManager.instance.onActivityResumed(activity)
                    Log.d("AA", "onActivityResumed")
                }
    
                override fun onActivityStarted(activity: Activity?) {
                    ScreenAutoManager.instance.onActivityStarted(activity)
                    Log.d("AA", "onActivityStarted")
                }
            })
        }
    

    其实 Resumed 和 Started 基本不用写

    1. app 内有2种适配

    啃爹的来了,这么设计绝对反人类,要是我我肯定不干。一种思路是大家在 base 基类里写一个适配的方法作为大部分页面适配的基础,然后不一样的代码,再重写该方法,另一种思路就是每个页面都写一遍

    宽度为基准

    ScreenAutoManager.instance.cleanResize(this)
    ScreenAutoManager.instance.resizeDisplayMetrics(this,1008f,ScreenAutoManager.BASE_LINE_WIDTH)
    

    高度为基准

    ScreenAutoManager.instance.cleanResize(this)
    ScreenAutoManager.instance.resizeDisplayMetrics(this,1920f,ScreenAutoManager.BASE_LINE_HRIGHT)
    

    为了排除相互影响,在设置自己页面的适配基准之前,先清除之前的适配基准,在 setContentView 之前调用


    代码参上

    class ScreenAutoManager {
    
        lateinit var application: Application
        var autoWidth: Float = 1080f
        var baseLine: Int = BASE_LINE_WIDTH
    
        companion object {
    
            var BASE_LINE_WIDTH: Int = 1
            var BASE_LINE_HRIGHT: Int = 2
    
            var instance: ScreenAutoManager = ScreenAutoManager()
        }
    
        fun init(application: Application, autoWidth: Float, baseLine: Int) {
            instance.application = application
            instance.autoWidth = autoWidth
            instance.baseLine = baseLine
            instance.resizeDisplayMetrics(application, autoWidth, baseLine)
        }
    
        fun isCanResize(context: Context?, baseLine: Int): Boolean {
    
            if (context == null) {
                return false
            }
    
            when (baseLine) {
                BASE_LINE_WIDTH -> {
                    if (Resources.getSystem().displayMetrics.xdpi == context.resources.displayMetrics.xdpi) {
                        return true
                    }
                }
                BASE_LINE_HRIGHT -> {
                    if (Resources.getSystem().displayMetrics.ydpi == context.resources.displayMetrics.ydpi) {
                        return true
                    }
                }
            }
    
            return false
        }
    
        fun resizeDisplayMetrics(context: Context?, autoWidth: Float, baseLine: Int) {
    
            if (context == null) {
                return
            }
    
            if (!isCanResize(context, baseLine)) {
                return
            }
    
            val screenPoint = Point()
            var resources = context.resources
            var displayMetricsonMiui = getMetricsOnMiui(resources)
    
            (context.getSystemService(WINDOW_SERVICE) as WindowManager).defaultDisplay.getSize(screenPoint)
    
            when (baseLine) {
    
                BASE_LINE_WIDTH -> {
                    resources.displayMetrics.xdpi = screenPoint.x / autoWidth * 72f
                    if (displayMetricsonMiui != null) {
                        resources.displayMetrics.xdpi = screenPoint.x / autoWidth * 72f
                    }
                }
    
                BASE_LINE_HRIGHT -> {
                    resources.displayMetrics.xdpi = screenPoint.y / autoWidth * 72f
                    if (displayMetricsonMiui != null) {
                        resources.displayMetrics.xdpi = screenPoint.y / autoWidth * 72f
                    }
                }
            }
        }
    
        fun getMetricsOnMiui(resources: Resources): DisplayMetrics? {
            if ("MiuiResources" == resources.javaClass.simpleName || "XResources" == resources.javaClass.simpleName) {
                try {
                    val field = Resources::class.java.getDeclaredField("mTmpMetrics")
                    field.isAccessible = true
                    return field.get(resources) as DisplayMetrics
                } catch (e: Exception) {
                    return null
                }
            }
            return null
        }
    
        fun cleanResize(context: Context) {
    
            context.resources.displayMetrics.xdpi = Resources.getSystem().displayMetrics.xdpi
    
            val metrics = getMetricsOnMiui(context.resources)
            metrics?.xdpi = Resources.getSystem().displayMetrics.xdpi
        }
    
        fun onActivityCreated(activity: Activity?) {
            //通常情况下application与activity得到的resource虽然不是一个实例,但是displayMetrics是同一个实例,只需调用一次即可
            //为了面对一些不可预计的情况以及向上兼容,分别调用一次较为保险
            resizeDisplayMetrics(application, autoWidth, baseLine)
            resizeDisplayMetrics(activity, autoWidth, baseLine)
        }
    
        fun onActivityStarted(activity: Activity?) {
            resizeDisplayMetrics(application, autoWidth, baseLine)
            resizeDisplayMetrics(activity, autoWidth, baseLine)
        }
    
        fun onActivityResumed(activity: Activity?) {
            resizeDisplayMetrics(application, autoWidth, baseLine)
            resizeDisplayMetrics(activity, autoWidth, baseLine)
        }
    
    }
    

    相关文章

      网友评论

        本文标题:屏幕适配 - pt 思路

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