美文网首页
Android屏幕适配方式探索

Android屏幕适配方式探索

作者: 移动开发的95后 | 来源:发表于2019-11-28 14:51 被阅读0次

    基本定义

    屏幕尺寸L

    例子:华为P10(VTR-AL00/全网通)这台手机的尺寸为5.1英寸
    含义:手机对角线的物理尺寸
    单位:英寸(inch),1英寸=2.54cm

    屏幕分辨率W*H

    例子:华为P10(VTR-AL00/全网通)的屏幕分辨率为1920x1080像素,即宽度方向上均匀填充1080个像素点,在高度方向上均匀填充1920个像素
    含义:手机在横向、纵向上的像素点数总和
    一般描述成屏幕的"宽x高”=AxB,屏幕在横向方向(宽度)上有A个像素点,在纵向方向(高)有B个像素点
    单位:px(pixel),UI设计师的设计图会以px作为统一的计量单位

    屏幕像素密度 dpi

    含义:每英寸的像素点数
    单位:dpi(dots per ich)
    假设设备内每英寸有160个像素,那么该设备的屏幕像素密度=160dpi

    密度无关像素dp

    含义:density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关。
    单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果
    Android开发时用dp而不是px单位设置图片大小,是Android特有的单位
    场景:假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在480x800分辨率手机上设置应为240px;在320x480的手机上 应设置为160px,二者设置就不同了;如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。

    dp与px的转换 px = density * dp ,density = dpi / 160

    在Android中,规定以160dpi(即屏幕分辨率为320x480)为基准:1dp=1px = 1px/density = 1px * 160 / dpi

    屏幕尺寸、分辨率、像素密度三者关系

    dpi = sqrt(W的平方+H的平方) / L = 对角线像素个数 / 对角线物理尺寸 = 一英寸的屏幕长度里填充了多少个像素点
    density = dpi / 160 = 一英寸的屏幕长度里填充了多少个像素点 乘以 一个固定常数
    所以对于每台手机来说,density值是有固定的物理意义的

    探索方案

    理解了上述定义,我们再往下看:

    约定设计规则

    在屏幕适配的问题上,首先要确保设计稿是按照一个恒定标准输出的

    比如按照一台屏幕宽高比为16:9,屏幕分辨率为1920*1080,density = 3的手机去设计UI
    px = density * dp ,由于规则恒定,我们可以根据设计稿给定的尺寸(像素)转化为(密度无关像素dp)

    我们可得知设计稿期望的是一个360dp(DESIGN_WIDTH)*640dp(DESIGN_HEIGHT)的画布(屏幕分辨率除以density即为像素与dp的换算结果)

    具体适配工作

    故适配工作就变成了我们要尽可能的将屏幕在宽度上的像素点分为360组,在高度上的像素点分为640组,每组计为一个dp,则适配者需要的做的就是改变系统的density值使px 与 dp 的换算比例发生变化,最终实现按照期望分组的愿望,density对于宽度与高度的像素转化关系是共用的,故对于16:9的机型下述两种方式都能得到期望的density值
    density = W/ DESIGN_WIDTH
    density = H / DESIGN_HEIGHT

    不同屏幕宽高比所面临的问题

    但这个愿望能实现的基础是屏幕的像素宽高比是16:9(640:360),对于大于或小于16:9的机型会出现什么问题呢?
    当我们处理一个屏幕分辨率比值大于16:9的机型时,
    如18:9(2340 1080)的机型时,
    按照宽度的分辨率改变density的值,则
    density = W / DESIGN_WIDTH = 3
    此时density的改变会影响H(屏幕宽度)的像素分组
    分组结果为 H / density = 2340 / 3 = 780dp
    该结果大于我们期望的640dp,此时会造成设计稿只占用了屏幕宽度分组总数720组的640组像素,使得80dp的空间为空白
    按照高度的分辨率改变density的值
    density = 2340 / 6440 = 3.65625
    此时density的改变会影响W(屏幕宽度)的像素分组
    分组结果为 W / density = 1080 / 3.65625 = 295.38dp
    则此时会造成设计稿占用了屏幕宽度分组总数295.38组的360组像素,使得部分设计稿的视图不足以展示在屏幕内而被遮挡显示不全

    当我们处理一个比值小于 16:9的机型时同理,若按照宽度的分辨率去改变density的值,则会使高度方向的设计内容有一部分被遮挡,
    若按照高度的分辨率去改变density的值,则会使宽度方向的设计内容展示完全后有一部分空白

    适应场景

    故针对此方案,需要根据实际设计需求,区分对当前activity来说,是宽度优先还是高度优先

    代码实现

    kotlin实现的工具类如下

    ...
    /**
    * description :Android屏幕适配方式 按照设计稿宽度为360dp*640dp处理
    * 解决问题场景:假设UI设计图按屏幕宽度360dp设计,在一个1920*1080、屏幕尺寸为5的手机上,
    * dpi为440(sqrt(宽的平方+高的平方)/尺寸),屏幕宽度其实为1080/(440/160)=392.7dp
    * 此时屏幕是比设计图要宽的,故此时在布局文件中使用dp也无法在不同设备上显示为同样效果,同时还存在部分设备屏幕宽度不足360dp导致实际显示不全的情况
    * Created by Wangpeng  on 2019-11-27
    */
    private var sSysDensity: Float =0f
    private var sSysScaledDensity: Float =0f
    private val DESIGN_WIDTH =360
    private val DESIGN_HEIGHT =640
    // 在Android中,规定以160dpi(即屏幕分辨率为320x480)为基准:1dp=1px
    private val ANDROID_DESIGN_STANTARD =160
    /**
    * 只关注宽度的精确适配
    */
    fun setCustomDensitySuitWidth(activity: Activity, application: Application) {
      setCustomDensity(activity, application, true)
    }
    /**
    * 只关注高度的精确适配
    */
    fun setCustomDensitySuitHeight(activity: Activity, application: Application) {
      setCustomDensity(activity, application, false)
    }
    
    private fun setCustomDensity(activity: Activity, application: Application, isSuitWidth: Boolean) {
      val appDisplayMetrics = application.resources.displayMetrics
      if (sSysDensity ==0f) {
      sSysDensity = appDisplayMetrics.density
      sSysScaledDensity = appDisplayMetrics.scaledDensity
      // 监听系统字体变化
      application.registerComponentCallbacks(object : ComponentCallbacks {
        override fun onConfigurationChanged(newConfig: Configuration?) {
          if (newConfig !=null &&newConfig.fontScale >0) {
            sSysScaledDensity = application.resources.displayMetrics.scaledDensity
          }
        }
        override fun onLowMemory() {}
        })
      }
      // 设计稿宽度为360dp,高度为640dp,屏幕宽度(像素为单位)除以设计稿宽度(dp为单位) 可以得到实际需要的density
      var targetDensity =if (isSuitWidth) {
        (appDisplayMetrics.widthPixels /DESIGN_WIDTH).toFloat()
      }else {
        (appDisplayMetrics.heightPixels /DESIGN_HEIGHT).toFloat()
      }
      val targetScaleDensity = targetDensity * (sSysScaledDensity /sSysDensity)
      val targetDensityDpi = (ANDROID_DESIGN_STANTARD * targetDensity).toInt()
      appDisplayMetrics.density = targetDensity
      appDisplayMetrics.scaledDensity = targetScaleDensity
      appDisplayMetrics.densityDpi = targetDensityDpi
      val atyDisplayMetrics = activity.resources.displayMetrics
      atyDisplayMetrics.density = targetDensity
      atyDisplayMetrics.scaledDensity = targetScaleDensity
      atyDisplayMetrics.densityDpi = targetDensityDpi
    }
    

    相关文章

      网友评论

          本文标题:Android屏幕适配方式探索

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