美文网首页
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